// ==UserScript==
// @name           KoC Allianz - Extras
// @namespace      PDX
// @description    Extra Tabs für die Allianz und Allianz Interne Links
// @homepage	   http://koc.god-like.org
// @version  	   0.3.7

// @include        http://*.kingdomsofcamelot.com/*main_src.php*
// @include        http://apps.facebook.com/kingdomsofcamelot/*

// @icon		   http://koc.god-like.info/pdx.jpg

// @require       http://koc.god-like.info/update/kocallianzextras.php?id=98788
// ==/UserScript==

var Version = '0.3.7';
var Versiondate = '19.04.2011';
var Scriptname = 'KoC Allianz - Extras';
var ScriptTabName = "'+ getMyAlliance()[1] +'";

var DEBUG_TRACE = false;
var ENABLE_ATTACK_TAB = false;
var ENABLE_SAMPLE_TAB = false;

var DISABLE_BULKADD_LIST = false;
var ENABLE_GM_AJAX_TRACE = false;

// end test switches

var MAP_DELAY = 1200;

var URL_CASTLE_BUT = 'http://koc.god-like.info/schloss1.png';
var URL_CASTLE_BUT_SEL = 'http://koc.god-like.info/schloss2.png';
//var CHAT_BG_IMAGE = 'http://i.imgur.com/VjNst.jpg';   // 600
var CHAT_BG_IMAGE = 'http://koc.god-like.info/chatbg.png';   // 720



var JSON;if(!JSON){JSON={};}(function(){"use strict";function f(n){return n<10?'0'+n:n;}if(typeof Date.prototype.toJSON!=='function'){Date.prototype.toJSON=function(key){return isFinite(this.valueOf())?this.getUTCFullYear()+'-'+f(this.getUTCMonth()+1)+'-'+f(this.getUTCDate())+'T'+f(this.getUTCHours())+':'+f(this.getUTCMinutes())+':'+f(this.getUTCSeconds())+'Z':null;};String.prototype.toJSON=Number.prototype.toJSON=Boolean.prototype.toJSON=function(key){return this.valueOf();};}var cx=/[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,escapable=/[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,gap,indent,meta={'\b':'\\b','\t':'\\t','\n':'\\n','\f':'\\f','\r':'\\r','"':'\\"','\\':'\\\\'},rep;function quote(string){escapable.lastIndex=0;return escapable.test(string)?'"'+string.replace(escapable,function(a){var c=meta[a];return typeof c==='string'?c:'\\u'+('0000'+a.charCodeAt(0).toString(16)).slice(-4);})+'"':'"'+string+'"';}function str(key,holder){var i,k,v,length,mind=gap,partial,value=holder[key];if(value&&typeof value==='object'&&typeof value.toJSON==='function'){value=value.toJSON(key);}if(typeof rep==='function'){value=rep.call(holder,key,value);}switch(typeof value){case'string':return quote(value);case'number':return isFinite(value)?String(value):'null';case'boolean':case'null':return String(value);case'object':if(!value){return'null';}gap+=indent;partial=[];if(Object.prototype.toString.apply(value)==='[object Array]'){length=value.length;for(i=0;i<length;i+=1){partial[i]=str(i,value)||'null';}v=partial.length===0?'[]':gap?'[\n'+gap+partial.join(',\n'+gap)+'\n'+mind+']':'['+partial.join(',')+']';gap=mind;return v;}if(rep&&typeof rep==='object'){length=rep.length;for(i=0;i<length;i+=1){k=rep[i];if(typeof k==='string'){v=str(k,value);if(v){partial.push(quote(k)+(gap?': ':':')+v);}}}}else{for(k in value){if(Object.hasOwnProperty.call(value,k)){v=str(k,value);if(v){partial.push(quote(k)+(gap?': ':':')+v);}}}}v=partial.length===0?'{}':gap?'{\n'+gap+partial.join(',\n'+gap)+'\n'+mind+'}':'{'+partial.join(',')+'}';gap=mind;return v;}}if(typeof JSON.stringify!=='function'){JSON.stringify=function(value,replacer,space){var i;gap='';indent='';if(typeof space==='number'){for(i=0;i<space;i+=1){indent+=' ';}}else if(typeof space==='string'){indent=space;}rep=replacer;if(replacer&&typeof replacer!=='function'&&(typeof replacer!=='object'||typeof replacer.length!=='number')){throw new Error('JSON.stringify');}return str('',{'':value});};}if(typeof JSON.parse!=='function'){JSON.parse=function(text,reviver){var j;function walk(holder,key){var k,v,value=holder[key];if(value&&typeof value==='object'){for(k in value){if(Object.hasOwnProperty.call(value,k)){v=walk(value,k);if(v!==undefined){value[k]=v;}else{delete value[k];}}}}return reviver.call(holder,key,value);}text=String(text);cx.lastIndex=0;if(cx.test(text)){text=text.replace(cx,function(a){return'\\u'+('0000'+a.charCodeAt(0).toString(16)).slice(-4);});}if(/^[\],:{}\s]*$/.test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,'@').replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,']').replace(/(?:^|:|,)(?:\s*\[)+/g,''))){j=eval('('+text+')');return typeof reviver==='function'?walk({'':j},''):j;}throw new SyntaxError('JSON.parse');};}}());
var JSON2 = JSON; 

//logit ("+++ STARTUP: "+ document.URL);

GlobalOptions = JSON2.parse (GM_getValue ('Options_??', '{}'));

if (document.URL.search(/apps.facebook.com\/kingdomsofcamelot/i) >= 0){
  facebookInstance ();

}


/***  Run only in "apps.facebook.com" instance ... ***/
function facebookInstance (){
  function setWide (){
    var iFrame = null;
    
    var e = document.getElementById('app_content_130402594779').firstChild.firstChild;
    for (var c=0; c<e.childNodes.length; c++){
      if (e.childNodes[c].tagName=='SPAN' && e.childNodes[c].firstChild.className == 'canvas_iframe_util'){
        iFrame = e.childNodes[c].firstChild; 
        break;
      }
    }
    if (!iFrame){
      var iframes = document.getElementsByTagName('iframe');
      for (var i=0; i<iframes.length; i++){
        if (iframes[i].className=='canvas_iframe_util'){
          iFrame = iframes[i];
          break; 
        }
      }
    }
    if (!iFrame){
      setTimeout (setWide, 1000);
      return;
    }
    try{    
      document.getElementById('sidebar_ads').parentNode.removeChild(document.getElementById('sidebar_ads'));
      document.getElementById('canvas_nav_content').parentNode.removeChild(document.getElementById('canvas_nav_content'));
    } catch (e){
      // toolkit may have removed them already!
    }
    var e = document.getElementById('content').firstChild;
    e.style.width = '100%';
    e.firstChild.style.width = '100%';
    iFrame.style.width = '100%';
  }
    
  facebookWatchdog();
  if (GlobalOptions.pcWideScreen)
    setWide();
}



var Options = {
  includeMarching:true,
  encRemaining : true,
  maxIdlePop   : false,	
  srcSortBy    : 'level',
  srcMinLevel  : 1,
  srcMaxLevel  : 7,
  wildType     : 1,
  unownedOnly  : true,
  pcWinIsOpen  : false,
  pcWinDrag    : true,
  pcWinPos     : {},
  pcTrackOpen  : true,
  pcKillFairie : false,
  pcGoldHappy  : 95,
  pcGoldEnable : false,
  pcEveryEnable: false,
  pcEveryMins  : 30,
  pcChatOnRight: false,
  pcWideMap    : false,
  giftDomains  : {valid:false, list:{}},
  giftDelete   : 'e',
  fixTower     : true,
  fixTower2    : true,
  fixMapDistance: true,
  fixMsgCount  : true,
  enhanceARpts : true,
  allowAlterAR : true,
  enhanceMsging : true,
  ptWinIsOpen  : false,
  ptWinDrag    : false,
  ptWinPos     : {},
  enableFoodWarn : true,
  foodWarnHours : 24,
  alertConfigpdx  : {
  	aChat:true, 
	aPrefix:'-= ACHTUNG = ANGRIFF =-', 
	scouting:true, 
	wilds:true, 
	minTroops:1, 
	spamLimit:10, 
	embassy:true, 
	mytroops:true, 
	food:true, 
	defense:true, 
	hq:'',
	sendEmail:true,
	emailAddress:'',
	token:'',
	sendasWhisper:true,
	sendtoAlly:true,
	},
};
unsafeWindow.pt_Options=Options;

var GlobalOptions = {
  pcWatchdog   : false,
  pcWideScreen : true,
};

var Cities = {};
var Seed = unsafeWindow.seed;
var Tabs = {};
var mainPop;
var pcStartupTimer = null;
var CPopUpTopClass = 'pcPopTop';


pcStartup ();

function pcStartup (){
  window.clearTimeout (pcStartupTimer);
  if (document.getElementById('pcOfficial') != null)
    return;
  var metd = getClientCoords(document.getElementById('main_engagement_tabs'));
  if (metd.width==null || metd.width==0){
    pcStartupTimer = window.setTimeout (pcStartup, 1000);
    return;
  }

  Seed = unsafeWindow.seed;
  var styles = '.xtab {padding-right: 5px; border:none; background:none; white-space:nowrap;}\
    .xtabBR {padding-right: 5px; border:none; background:none;}\
    table.pcTab tr td {border:none; background:none; white-space:nowrap; padding:0px}\
    table.pcTabPadNW tr td {border:none; background:none; white-space:nowrap; padding: 2px 4px 2px 8px;}\
    table.pcTabBR tr td {border:none; background:none;}\
    table.pcTabLined tr td {border:1px none none solid none; padding: 2px 5px; white-space:nowrap;}\
    table.pcTabPad tr td.pcentry {background-color:none; padding-left: 8px;}\
    table.ptNoPad tr td {border:none; background:none; white-space:nowrap; padding:0px}\
    .pcstat {border:1px solid; border-color:#ffffff; font-weight:bold; padding-top:2px; padding-bottom:2px; text-align:center; color:#ffffff; background-color:#357}\
    .pcStatLight {color:#ddd}\
    .pcentry {padding: 7px; border:1px solid; border-color:#000000; background-color:#ffeecc; white-space:nowrap;}\
    .ptErrText {font-weight:bold; color:#600000}\
    .castleBut {outline:0px; margin-left:0px; margin-right:0px; width:24px; height:26px; font-size:12px; font-weight:bold;}\
    .castleBut:hover {border-size:3px; border-color:#000;}\
    button::-moz-focus-inner, input[type="submit"]::-moz-focus-inner { border: none; }\
    span.whiteOnRed {padding-left:3px; padding-right:3px; background-color:#700; color:white; font-weight:bold}\
    span.boldRed {color:#800; font-weight:bold}\
    .castleButNon {background-image:url("'+ URL_CASTLE_BUT +'")}\
    .castleButSel {background-image:url("'+ URL_CASTLE_BUT_SEL +'")}\
    a.ptButton20 {color:#ffff80}\
    table.pcMainTab {empty-cells:show; margin-top:5px }\
    table.pcMainTab tr td a {color:inherit }\
    table.pcMainTab tr td   {height:60%; empty-cells:show; padding: 0px 5px 0px 5px;  margin-top:5px; white-space:nowrap; border: 1px solid; border-style: none none solid none; }\
    table.pcMainTab tr td.spacer {padding: 0px 4px;}\
    table.pcMainTab tr td.sel    {font-weight:bold; font-size:13px; border: 1px solid; border-style: solid solid none solid; background-color:#eed;}\
    table.pcMainTab tr td.notSel {font-weight:bold; font-size:13px; border: 1px solid; border-style: solid solid none solid; background-color:#DF7401; color:white; border-color:black;}\
    tr.pcPopTop td { background-color:#FFFFFF; border:none; height: 21px;  padding:0px; }\
    tr.pcretry_pcPopTop td { background-color:#FF0000; color:#fff; border:none; height: 21px;  padding:0px; }\
    .CPopup .CPopMain { background-color:#f8f8f8; padding:6px;}\
    .CPopup  {border:3px ridge #666}\
    div.indent25 {padding-left:25px}';
    
    
  window.name = 'PDXAE';
  logit ("* "+Scriptname +" by PDX v"+ Version +" geladen!");
  readOptions();
  readGlobalOptions ();
  setCities();

// TODO: Make sure WinPos is visible on-screen ?
  if (Options.pcWinPos==null || Options.pcWinPos.x==null|| Options.pcWinPos.x=='' || isNaN(Options.pcWinPos.x)){
    var c = getClientCoords (document.getElementById('main_engagement_tabs'));
    Options.pcWinPos.x = c.x+4;
    Options.pcWinPos.y = c.y+c.height;
    saveOptions ();
  }
  mainPop = new CPopup ('pc', Options.pcWinPos.x, Options.pcWinPos.y, 750,750, Options.pcWinDrag, 
      function (){
        tabManager.hideTab();
        Options.pcWinIsOpen=false; 
        saveOptions()
      });
  mainPop.autoHeight (true);  

  mainPop.getMainDiv().innerHTML = '<STYLE>'+ styles +'</style>';
  tabManager.init (mainPop.getMainDiv());
  actionLog (""+Scriptname +" by PDX v"+ Version +" geladen!");
  
  
  //FairieKiller.init (Options.pcKillFairie);
  //RefreshEvery.init ();
  //CollectGold.init();
  
  if (Options.pcWinIsOpen && Options.pcTrackOpen){
    mainPop.show (true);
    tabManager.showTab();
  }
  window.addEventListener('unload', onUnload, false);
  TowerAlerts.init();
  TowerAlerts.setPostToChatOptions(Options.alertConfigpdx);
  AddMainTabLink(''+ getMyAlliance()[1] + '', eventHideShow, mouseMainTab);
  kocWatchdog ();
}
/*********************************** Options Tab ***********************************/
Tabs.Options = {
  tabOrder: 20,
  tabLabel : 'Einstellung',
  myDiv : null,
  fixAvailable : {},

  init : function (div){
    var t = Tabs.Options;
    t.myDiv = div;


    // CitySelect
    var citySelect = '   <SELECT id=pcalertHQ class=pcalertHQ name=pcalertHQ   onchange="saveOptions();">"';

    for (var c=0; c<Cities.numCities; c++) {

	 	 aCity = Cities.cities[c].name + ' ('+Cities.cities[c].x + ','+ Cities.cities[c].y+')';
         citySelect += '<option value=\''+ aCity +'\' '+ (Options.alertConfigpdx.hq==aCity?' SELECTED ':'') +'>'+aCity+'</option>';
	     }   
		 	citySelect += '</select>';
    try {      
      m = '<DIV style="height:550px; max-height:550px; overflow-y:auto"><TABLE width=auto class=pcTabLined cellspacing=0 cellpadding=0>\
        <TR><TD colspan=2><B>Wachturm - Einstellung</b></td></tr>\
        <TR><TD><INPUT id=pcalertEnable type=checkbox '+ (Options.alertConfigpdx.aChat?' ':'') +'/></td><TD>Automatisch Angriffe im Allianz Chat posten.</td></tr>\
	          <TR><TD></td><TD><TABLE cellpadding=0 cellspacing=0>\
	              <TR><TD align=right>Nachrichten Prefix: &nbsp; </td><TD><INPUT id=pcalertPrefix type=text size=60 maxlength=120 value="'+ Options.alertConfigpdx.aPrefix +'" \></td></tr>\
	              <TR><TD align=right>beim Auspähen: &nbsp; </td><TD><INPUT id=pcalertScout type=checkbox '+ (Options.alertConfigpdx.scouting?' ':'') +'/></td></tr>\
	              <TR><TD align=right>bei Wildniss Angriffen: &nbsp; </td><TD><INPUT id=pcalertWild type=checkbox '+ (Options.alertConfigpdx.wilds?' ':'') +'/></td></tr>\
	              <TR><TD align=right>bei Truppenstärke: &nbsp; </td><TD><INPUT id=pcalertTroops type=text size=7 value="'+ Options.alertConfigpdx.minTroops +'" \> &nbsp; &nbsp; <span id=pcalerterr></span></td></tr>\
	              <TR><TD align=right>Truppen in der Botschaft Anzeigen: &nbsp; </td><TD><INPUT id=pcalertEmbassy type=checkbox '+ (Options.alertConfigpdx.embassy?' ':'') +'/></td></tr>\
	              <TR><TD align=right>Eigene Truppen Anzeigen: &nbsp; </td><TD><INPUT id=pcalertMytroops type=checkbox '+ (Options.alertConfigpdx.mytroops?' ':'') +'/></td></tr>\
	              <TR><TD align=right>Nahrungs Verbrauch Anzeigen: &nbsp; </td><TD><INPUT id=pcalertFood type=checkbox '+ (Options.alertConfigpdx.food?' ':'') +' /></td></tr>\
	              <TR><TD align=right>Mauerdef Anzeigen: &nbsp; </td><TD><INPUT id=pcalertDefense type=checkbox '+ (Options.alertConfigpdx.defense?' ':'') +' /></td></tr>\
	              <TR><TD align=right>Mein Bunker Anzeigen: &nbsp; </td><TD>'+ citySelect +'&nbsp; &nbsp;</td></tr>\
	              <TR><TD align=right>Nachrichten im Allianz Chat posten: &nbsp; </td><TD><INPUT id=pcalertSendToAlly type=checkbox '+ (Options.alertConfigpdx.sendtoAlly?' ':'') +' /></td></tr>\
				  <TR><TD align=right>Mich selbst anflüstern: &nbsp; </td><TD><INPUT id=pcalertSendAsWhisper type=checkbox '+ (Options.alertConfigpdx.sendasWhisper?' ':'') +' /></td></tr>\
				  <TR><TD align=right>E-Mail bei Angriffen: &nbsp; </td><TD><INPUT id=pcalertSendEmail type=checkbox '+ (Options.alertConfigpdx.sendEmail?' ':'') +' /></td></tr>\
		          <TR><TD align=right>E-Mail Adresse: &nbsp; </td><TD><INPUT id=pcalertEmailAddress type=text size=36 value="'+ Options.alertConfigpdx.emailAddress +'" \> &nbsp; &nbsp;</td></tr>\
				  <TR><TD align=right>Mein Token: &nbsp; </td><TD><INPUT id=pcalertToken type=text size=36 value="'+ Options.alertConfigpdx.token +'" \> &nbsp; &nbsp;</td></tr>\
	              </table></td></tr>\
        </table><BR><BR><HR>Hinweis: Wenn eine Box nicht klickbar ist hat KoC mal wieder ein Update gemacht!<BR><font color=#FF0000><u>WICHTIG</u></font>: Damit der Gebiete Tab Funktioniert musst du dieses Tool (<a href="http://userscripts.org/scripts/show/96308" target="_blank">KOCtools: The Script</a>) Instalieren und Laufen Lassen<BR>(Mach es aber aus solange du den Gebiete Tab nicht benutz!)<HR>KoC - Allianz Extras - Update: <a href="http://userscripts.org/scripts/show/98788" target="_blank">KoC Allianz - Extras</a> - Version: '+ Version +'</div>';
      div.innerHTML = m;
	  
t.togOpt ('pcalertEnable', 'pcalertEnable', Options.alertConfigpdx.aChat.setEnable);
t.changeOpt ('pcalertPrefix', 'pcalertPrefix', Options.alertConfigpdx.aPrefix.setEnable);
t.togOpt ('pcalertScout', 'pcalertScout', Options.alertConfigpdx.scouting.setEnable);
t.togOpt ('pcalertWild', 'pcalertWild', Options.alertConfigpdx.wilds.setEnable);
t.changeOpt  ('pcalertTroops', 'pcalertTroops');
t.togOpt ('pcalertFood', 'pcalertFood', Options.alertConfigpdx.food.setEnable);	  
t.togOpt ('pcalertEmbassy', 'pcalertEmbassy', Options.alertConfigpdx.embassy.setEnable);	  
t.togOpt ('pcalertMytroops', 'pcalertMytroops', Options.alertConfigpdx.mytroops.setEnable);	
t.togOpt ('pcalertDefense', 'pcalertDefense', Options.alertConfigpdx.defense.setEnable);
//Options.alertConfigpdx.hq=document.getElementById('pcalertHQ').options[document.getElementById('pcalertHQ').selectedIndex].value;
t.changeOpt ('pcalertHQ', 'pcalertHQ');
t.togOpt ('pcalertSendEmail', 'pcalertSendEmail', Options.alertConfigpdx.sendEmail.setEnable);
t.togOpt ('pcalertEmailAddress', 'pcalertEmailAddress', Options.alertConfigpdx.emailAddress.setEnable);
t.togOpt ('pcalertToken', 'pcalertToken', Options.alertConfigpdx.token.setEnable);
t.togOpt ('pcalertSendAsWhisper', 'pcalertSendAsWhisper', Options.alertConfigpdx.sendasWhisper.setEnable);
t.togOpt ('pcalertSendToAlly', 'pcalertSendToAlly', Options.alertConfigpdx.sendtoAlly.setEnable);	


    } catch (e) {
      div.innerHTML = '<PRE>'+ e.name +' : '+ e.message +'</pre>';  
    }      
  },

  hide : function (){         // called whenever the main window is hidden, or another tab is selected
    var t = Tabs.Options;
    mainPop.div.style.height = 550 + 'px';
    mainPop.div.style.width = 750 + 'px';
  },
  
  show : function (){         // called whenever this tab is shown
    var t = Tabs.Options;
    mainPop.div.style.height = 550 + 'px';
    mainPop.div.style.width = 750 + 'px';
  },

  togOpt : function (checkboxId, optionName, callEnable, callIsAvailable, callOnChange){
    var t = Tabs.Options;
    var checkbox = document.getElementById(checkboxId);
 if (Options[optionName])
      checkbox.checked = true;
    checkbox.addEventListener ('change', new eventToggle(checkboxId, optionName, callEnable).handler, false);
    function eventToggle (checkboxId, optionName, callOnChange){
      this.handler = handler;
      var optName = optionName;
      var callback = callOnChange;
      function handler(event){
        Options[optionName] = this.checked;
        saveOptions();
        if (callback != null)
          callback (this.checked);
    }
  }
  },

  changeOpt : function (valueId, optionName, callOnChange){
    var t = Tabs.Options;
    var e = document.getElementById(valueId);
    e.value = Options[optionName];
    e.addEventListener ('change', eventHandler, false);
    function eventHandler (){
      Options[optionName] = this.value;
      saveOptions();
      if (callOnChange)
        callOnChange (this.value);
    }
  },
  
  e_watchChanged : function (){
    GlobalOptions.pcWatchdog = document.getElementById('pcWatchEnable').checked;
    GM_setValue ('Options_??', JSON2.stringify(GlobalOptions));  
  },
  
  e_wideChanged : function (){
    GlobalOptions.pcWideScreen = document.getElementById('pcWideOpt').checked;
    GM_setValue ('Options_??', JSON2.stringify(GlobalOptions));  
  },
}
/****************************  Website Tab   ******************************/
Tabs.board = {
  tabOrder : 50,                    // order to place tab in top bar
  tabLabel : 'Website',            // label to show in main window tabs
  myDiv : null,
  timer : null,
  forumlink :  'http://kocscripts.god-like.org',  
  
  init : function (div){    // called once, upon script startup
    var t = Tabs.board;
    t.myDiv = div;
    div.innerHTML = '<CENTER><iframe src="'+ t.forumlink +'" width="100%" height="500" name="board_in_a_box" <p>Ihr Browser kann leider keine eingebetteten Frames anzeigen: Sie k&ouml;nnen die eingebettete Seite &uuml;ber den folgenden Verweis aufrufen: <a href="'+ t.forumlink +'">KoC Allianz - Extras</a></p> </iframe> <BR></center>';
  },
  
  hide : function (){         // called whenever the main window is hidden, or another tab is selected
    var t = Tabs.board;
    mainPop.div.style.width = 750 + 'px';
  },
  
  show : function (){         // called whenever this tab is shown
    var t = Tabs.board;
    mainPop.div.style.width = 850 + 'px';
  },
}
/****************************  Teamspeak Tab   ******************************/
Tabs.teamspeak = {
  tabOrder : 53,                    // order to place tab in top bar
  tabLabel : 'TS³',            // label to show in main window tabs
  myDiv : null,
  timer : null,
  forumlink :  'http://ts3.god-like.info/tsviewpub.php?skey=0&port=31337&showicons=right&bgcolor=none&fontcolor=000000',  
  
  init : function (div){    // called once, upon script startup
    var t = Tabs.teamspeak;
    t.myDiv = div;
    div.innerHTML = '<CENTER><iframe allowtransparency="true" src="'+ t.forumlink +'" width="100%" height="500" name="board_in_a_box" <p>Ihr Browser kann leider keine eingebetteten Frames anzeigen: Sie k&ouml;nnen die eingebettete Seite &uuml;ber den folgenden Verweis aufrufen: <a href="'+ t.forumlink +'">god-like.org - Teamspeak Server</a></p> </iframe> <BR></center>';
  },
  
  hide : function (){         // called whenever the main window is hidden, or another tab is selected
    var t = Tabs.teamspeak;
    mainPop.div.style.width = 750 + 'px';
  },
  
  show : function (){         // called whenever this tab is shown
    var t = Tabs.teamspeak;
    mainPop.div.style.width = 750 + 'px';
  },
}

/****************************  Map Tab   ******************************/
Tabs.map = {
  tabOrder : 40,                    // order to place tab in top bar
  tabLabel : 'Gebiete',            // label to show in main window tabs
  myDiv : null,
  timer : null,
  forumlink :  'http://koctools.com/index.php?pageid=servers',  
  
  init : function (div){    // called once, upon script startup
    var t = Tabs.map;
    t.myDiv = div;
    div.innerHTML = '<CENTER><iframe src="'+ t.forumlink +'" width="100%" height="500" name="board_in_a_box" <p>Ihr Browser kann leider keine eingebetteten Frames anzeigen: Sie k&ouml;nnen die eingebettete Seite &uuml;ber den folgenden Verweis aufrufen: <a href="'+ t.forumlink +'">KoC Server Gebiete</a></p> </iframe> <BR></center>';
  },
  
  hide : function (){         // called whenever the main window is hidden, or another tab is selected
    var t = Tabs.map;
    mainPop.div.style.width = 750 + 'px';
  },
  
  show : function (){         // called whenever this tab is shown
    var t = Tabs.map;
    mainPop.div.style.width = 1100 + 'px';
  },
}
/****************************  Mappa tab   ******************************/
Tabs.Mappa = {
  tabOrder : 42,                    // order to place tab in top bar
  tabLabel : 'Karte',            // label to show in main window tabs
  myDiv : null,
  timer : null,
  forumlink :  'http://koc.dunno.com/index.sjs?f=KocMapViewer&server_id=179',  
  
  init : function (div){    // called once, upon script startup
    var t = Tabs.Mappa;
    t.myDiv = div;
    div.innerHTML = '<CENTER><iframe src="'+ t.forumlink +'" width="100%" height="550" name="board_in_a_box" <p>Ihr Browser kann leider keine eingebetteten Frames anzeigen: Sie k&ouml;nnen die eingebettete Seite &uuml;ber den folgenden Verweis aufrufen: <a href="'+ t.forumlink +'">Truppen Spostamenti</a></p> </iframe> <BR></center>';
  },
  
  hide : function (){         // called whenever the main window is hidden, or another tab is selected
    var t = Tabs.Mappa;
    mainPop.div.style.width = 750 + 'px';
  },
  
  show : function (){         // called whenever this tab is shown
    var t = Tabs.Mappa;
    mainPop.div.style.width = 1100 + 'px';
  },
}

/****************************  Script Tab   ******************************/
Tabs.script = {
  tabOrder : 70,                    // order to place tab in top bar
  tabLabel : 'Scripts',            // label to show in main window tabs
  myDiv : null,
  timer : null,
  forumlink :  'http://userscripts.org/users/297645/scripts',  
  
  init : function (div){    // called once, upon script startup
    var t = Tabs.script;
    t.myDiv = div;
    div.innerHTML = '<CENTER><iframe src="'+ t.forumlink +'" width="100%" height="500" name="board_in_a_box" <p>Ihr Browser kann leider keine eingebetteten Frames anzeigen: Sie k&ouml;nnen die eingebettete Seite &uuml;ber den folgenden Verweis aufrufen: <a href="'+ t.forumlink +'">My Userscripts.org</a></p> </iframe> <BR></center>';
  },
  
  hide : function (){         // called whenever the main window is hidden, or another tab is selected
    var t = Tabs.script;
    mainPop.div.style.width = 750 + 'px';
  },
  
  show : function (){         // called whenever this tab is shown
    var t = Tabs.script;
    mainPop.div.style.width = 1100 + 'px';
  },
}
/****************************  Rechner Tab   ******************************/
Tabs.rechner = {
  tabOrder : 30,                    // order to place tab in top bar
  tabLabel : 'Rechner',            // label to show in main window tabs
  myDiv : null,
  timer : null,
  forumlink :  'http://koc.god-like.info/rechner.html',  
  
  init : function (div){    // called once, upon script startup
    var t = Tabs.rechner;
    t.myDiv = div;
    div.innerHTML = '<CENTER><iframe src="'+ t.forumlink +'" width="100%" height="500" name="board_in_a_box" <p>Ihr Browser kann leider keine eingebetteten Frames anzeigen: Sie k&ouml;nnen die eingebettete Seite &uuml;ber den folgenden Verweis aufrufen: <a href="'+ t.forumlink +'">Truppen Rechner</a></p> </iframe> <BR></center>';
  },
  
  hide : function (){         // called whenever the main window is hidden, or another tab is selected
    var t = Tabs.rechner;
    mainPop.div.style.width = 750 + 'px';
  },
  
  show : function (){         // called whenever this tab is shown
    var t = Tabs.rechner;
    mainPop.div.style.width = 750 + 'px';
  },
}

/****************************  Wikia Tab   ******************************/
Tabs.wikia = {
  tabOrder : 60,                    // order to place tab in top bar
  tabLabel : 'Wiki',            // label to show in main window tabs
  myDiv : null,
  timer : null,
  forumlink :  'http://www.kingdoms-of-camelot.de',  
  
  init : function (div){    // called once, upon script startup
    var t = Tabs.wikia;
    t.myDiv = div;
    div.innerHTML = '<CENTER><iframe src="'+ t.forumlink +'" width="100%" height="500" name="board_in_a_box" <p>Ihr Browser kann leider keine eingebetteten Frames anzeigen: Sie k&ouml;nnen die eingebettete Seite &uuml;ber den folgenden Verweis aufrufen: <a href="'+ t.forumlink +'">Kingdom of Camelot - Wiki - Deutsch</a></p> </iframe> <BR></center>';
  },
  
  hide : function (){         // called whenever the main window is hidden, or another tab is selected
    var t = Tabs.wikia;
    mainPop.div.style.width = 750 + 'px';
  },
  
  show : function (){         // called whenever this tab is shown
    var t = Tabs.wikia;
    mainPop.div.style.width = 1100 + 'px';
  },
}


/****************************  External Chat Tab   ******************************/
Tabs.extChat = {
  tabOrder : 50,                    // order to place tab in top bar
  tabLabel : 'Chat',            // label to show in main window tabs
  myDiv : null,
  timer : null,
  chatlink :  'http://webchat.quakenet.org/?channels=PDX&uio=Mj10cnVlJjQ9dHJ1ZSY5PXRydWUmMTA9dHJ1ZSYxMT0zNDkmMTI9dHJ1ZQ57',  
  
  init : function (div){    // called once, upon script startup
    var t = Tabs.extChat;
    t.myDiv = div;
    div.innerHTML = '<CENTER><iframe src="'+ t.chatlink +'" width="100%" height="500" name="board_in_a_box" <p>Ihr Browser kann leider keine eingebetteten Frames anzeigen: Sie k&ouml;nnen die eingebettete Seite &uuml;ber den folgenden Verweis aufrufen: <a href="'+ t.chatlink +'">Allianz Chat</a></p> </iframe> <BR></center>';
  },
  
  hide : function (){         // called whenever the main window is hidden, or another tab is selected
    var t = Tabs.extChat;  	  
    mainPop.div.style.width = 750 + 'px';
  },
  
  show : function (){         // called whenever this tab is shown
    var t = Tabs.extChat;  
    mainPop.div.style.width = 750 + 'px';

  },
}

/*********************************** Log Tab ***********************************/
Tabs.ActionLog = {
  tabOrder: 80,
  tabLabel : 'Log',
  myDiv : null,
  logTab : null,
  maxEntries: 300,
  last50 : [],
  state : null,
    
  init : function (div){
    var t = Tabs.ActionLog;
    t.myDiv = div;
    t.myDiv.innerHTML = '<DIV class=pcstat>ACTION LOG</div><DIV style="height:500px; max-height:500px; overflow-y:auto">\
      <TABLE cellpadding=0 cellspacing=0 id=pcactionlog class=pcTabLined><TR><TD></td><TD width=95%></td></table></div>';
    t.logTab = document.getElementById('pcactionlog');  
    t.state = 1;
    var a = JSON2.parse(GM_getValue ('log_'+getServerId(), '[]'));
    if (matTypeof(a) == 'array'){
      t.last50 = a;
      for (var i=0; i<t.last50.length; i++)
        t._addTab (t.last50[i].msg, t.last50[i].ts);
    }
    window.addEventListener('unload', t.onUnload, false);
  },

  hide : function (){         // called whenever the main window is hidden, or another tab is selected
    var t = Tabs.ActionLog;
    mainPop.div.style.width = 750 + 'px';
  },
  
  show : function (){         // called whenever this tab is shown
    var t = Tabs.ActionLog;
    mainPop.div.style.width = 750 + 'px';
  },


  onUnload : function (){
    var t = Tabs.ActionLog;
    GM_setValue ('log_'+getServerId(), JSON2.stringify(t.last50));
  },
    
  _addTab : function (msg, ts){
    var t = Tabs.ActionLog;
    if (t.state != 1)
      return;
    if (t.logTab.rows.length >= t.maxEntries)
      t.logTab.deleteRow(t.maxEntries-1);
    var row = t.logTab.insertRow(0);
    row.vAlign = 'top';
    row.insertCell(0).innerHTML = ts;
    row.insertCell(1).innerHTML = msg;
  }, 
  
  log : function (msg){
    var t = Tabs.ActionLog;
    var ts = new Date().toTimeString().substring (0,8);
    t._addTab (msg, ts);
    while (t.last50.length >= 50)
      t.last50.shift();
    t.last50.push ({msg:msg, ts:ts});
  },
}

function actionLog (msg){
  if (!Tabs.ActionLog.tabDisabled)
    Tabs.ActionLog.log (msg);  
}

/************************ Tower Alerts ************************/
var TowerAlerts = {
  generateIncomingFunc : null,
  fixTargetEnabled : false,
  towerMarches : {},    // track all marches that have been posted to alliance chat
  
  init : function (){
    var t = TowerAlerts; 
    var s = GM_getValue ('towerMarches_'+getServerId());
    if (s != null)
      t.towerMarches = JSON2.parse (s);

    t.generateIncomingFunc = new CalterUwFunc ('attack_generateincoming', [[/.*} else {\s*e = true;\s*}/im, '} else { e = ptGenerateIncoming_hook(); }']]);
    unsafeWindow.ptGenerateIncoming_hook = t.generateIncoming_hook;
  },
  
  postToChatOptions : {aChat:false},
  secondTimer : null,

  setPostToChatOptions : function (obj){
    var t = TowerAlerts;
    t.postToChatOptions = obj;
    clearTimeout(t.secondTimer);
	 if (obj.aChat)
		t.e_eachSecond();
	//DD two lines deleted
  },
    
  addTowerMarch : function (m){
    var t = TowerAlerts;
    var now = unixTime();
    for (k in t.towerMarches){
      if (t.towerMarches[k].arrival < now)
        delete t.towerMarches[k];
    }
    t.towerMarches['m'+m.mid] = {added:now, arrival:parseIntNan(m.arrivalTime) };
    GM_setValue ('towerMarches_'+getServerId(), JSON2.stringify(t.towerMarches) );
  },
  
  getTowerMarch : function (mid){ // ID only (no 'm')
    var t = TowerAlerts;
    return t.towerMarches['m'+mid];
  },

  e_eachSecond : function (){   // check for incoming marches
    var t = TowerAlerts;
	var now = unixTime();
    if (matTypeof(Seed.queue_atkinc) != 'array'){
      for (var k in Seed.queue_atkinc){
        var m = Seed.queue_atkinc[k]; 
        if ((m.marchType==3 || m.marchType==4) && parseIntNan(m.arrivalTime)>now && t.getTowerMarch(m.mid)==null){
          t.addTowerMarch (m);
          t.postToChat (m, false);
        }
      }
    }
    t.secondTimer = setTimeout (t.e_eachSecond, 2000);
  },
  

	postToChat: function(m, force){
		var t = TowerAlerts;
		//var postOptions = Options.alertConfig;
		var postOptions = t.postToChatOptions;
		var troopsnames = ['Versorger', 'Miliz', 'Späher', 'Lanzen', 'Schwerter', 'Bogis', 'Kav', 'SKav', 'Wagen', 'Ballis', 'Ram', 'Katapulte'];
		var tot = [];

		for (i = 0; i < 13; i++) 
			tot[i] = 0;

		if (DEBUG_TRACE) {
			logit("checkTower(): INCOMING bei " + unixTime() + ": \n" + inspect(m, 8, 1));
		}	
		if (m.marchType == null) {// bogus march (returning scouts)
			if (DEBUG_TRACE) {
			logit("checkTower(): m.marchType == null");
			}	
			return;
		}	

			
		if (m.marchType == 3) {
			if (!postOptions.scouting && !force) 
				return;
			atkType = 'Spähen';
			if (DEBUG_TRACE) {
			logit("checkTower(): scout");
			}	
		}
		else {
			if (m.marchType == 4) {
				atkType = 'Angriff';
				if (DEBUG_TRACE) {
				logit("checkTower(): attack");
				}
			}
			else {
				if (DEBUG_TRACE) {
				logit("checkTower(): unkown march typ");
				}
				return;
			}
		}
		
		if (DEBUG_TRACE) {
			logit("checkTower(): after typ");
		}	
		var target, atkType, who;
		var city = Cities.byID[m.toCityId];
		if (city.tileId == m.toTileId) 
			target = ' ' + city.name + ' (' + city.x + ',' + city.y+') ';
		else {
			if (!postOptions.wilds && !force) 
				return;
			target = 'Wildniss';
			for (k in Seed.wilderness['city' + m.toCityId]) {
				if (Seed.wilderness['city' + m.toCityId][k].tileId == m.toTileId) {
					target += '(' + Seed.wilderness['city' + m.toCityId][k].xCoord + ',' + Seed.wilderness['city' + m.toCityId][k].yCoord+') ';
					break;
				}
			}
		}
		
		
		var attackermight = '';
		var allianceId = '';
		
		if (Seed.players['u' + m.pid]) {
			who = Seed.players['u' + m.pid].n;
			attackermight = parseInt(Seed.players['u' + m.pid].m);
            allianceId = Seed.players['u' + m.pid].a;
		} else {
			if (m.players && m.players['u' + m.pid]) {
				who = m.players['u' + m.pid].n;
				attackermight = parseInt(m.players['u' + m.pid].m);
                allianceId = m.players['u' + m.pid].a;
			} else {
				who = 'Unknown';
			}
		}
		
		if (m.fromXCoord) 
			who += ' bei ' + m.fromXCoord + ',' + m.fromYCoord;
			
		var diplomacy = getDiplomacy(allianceId);

		
		var arrivingDatetime = new Date();
		arrivingDatetime.setTime(m.arrivalTime * 1000);
		
		if (DEBUG_TRACE) {
			logit("checkTower(): Before Messagecreate");
		}
		/** ** message ** */
		var msg = '';
		var subject = '';

		if (!force) 
			msg = postOptions.aPrefix + ' ';
		
		msg += 'Mein Bunker: ' + postOptions.hq + ' = ';
		msg += 'Marsch Typ: ' + atkType + ' = ';
		msg += 'Ziel: ' + target + ' = ';
		msg += 'Angreifer: ' + who + ' (' + addCommas(attackermight) + ', ' + diplomacy + ') ';
		
		msg +='= Truppenstärke vom Angreifer = ';
		var totTroops = 0;
		for (k in m.unts) {
			var uid = parseInt(k.substr(1));
			msg += m.unts[k] + ' ' + unsafeWindow.unitcost['unt' + uid][0] + ', ';
			totTroops += m.unts[k];
		}
		
		if ((totTroops < postOptions.minTroops) && !force) 
			return;
		
		msg = msg.slice(0, -2);
		msg += ' (Ankunft in ' + unsafeWindow.timestr(parseInt(m.arrivalTime - unixTime())) + ') ' ;
		
		/** ** Embassy** **/
		if (city.tileId == m.toTileId) {
			var emb = getCityBuilding(m.toCityId, 8);
			if (emb.count > 0) {
				var availSlots = emb.maxLevel;
				for (k in Seed.queue_atkinc) {
					if (Seed.queue_atkinc[k].marchType == 2 && Cities.byID[Seed.queue_atkinc[k].fromCityId] == null) {
						--availSlots;
					}
				}
				msg += '= Botschaft (' + availSlots + '/' + emb.maxLevel + ')Slots =';
				
				if (DEBUG_TRACE) {
					logit("checkTower(): Mesage Part1:"+msg);
				}
				var msgpart1 = msg;
				msg ='';
				if (DEBUG_TRACE) {
					logit("checkTower(): in Embassy: "+postOptions.embassy);
				   }
				if (postOptions.embassy) {
					var rownum = 0;
					enc = {};
					numSlots = 0;

					if (matTypeof(Seed.queue_atkinc) != 'array') {
						for (k in Seed.queue_atkinc) {
							march = Seed.queue_atkinc[k];
							if (march.marchType == 2) {
								++numSlots;
								city = march.toCityId;
								from = march.fromPlayerId;
								if (!enc[city]) 
									enc[city] = {};
								if (!enc[city][from]) 
									enc[city][from] = [];
								s = [];
								s[0] = parseInt(march.knightCombat);
								for (i = 1; i < 13; i++) {
									if (Options.encRemaining) 
										s[i] = parseInt(march['unit' + i + 'Return']);
									else 
										s[i] = parseInt(march['unit' + i + 'Count']);
								}
								enc[city][from].push(s);
							}
						}
					}
					
										
					dest = m.toCityId;
					var embassyOccupied = false;
					if (enc[dest]) {
						embassyOccupied = true;
						msg += ' Truppen in der Botschaft: ';
						for (p in enc[dest]) {
							try {
								if (Seed.players['u' +p]) {
									player = Seed.players['u' + p].n;
								} else {
									if (m.players && m.players['u' + p]) {
										player = m.players['u' + p].n;
									} else {
										player = 'Unknown';
									}
								}

							} 
							catch (err) {
								player = '???';
							}
							for (j = 0; j < enc[dest][p].length; j++) {
								knight = '';
								if (enc[dest][p][j][0] > 0) 
									knight = ' (' + enc[dest][p][j][0] + ')';
								var slot = j + 1;
								msg += '[Slot' + slot + ': ' + player + knight + ' - ';
								for (i = 1; i < 13; i++) {
									num = enc[dest][p][j][i];
									if (num > 0) 
										msg += '' + addCommas(num) + ' ' + troopsnames[i - 1] + ', ';
									tot[i] += num;
								}
								msg += ']';
							}//for (j=0; j<enc[dest][p].length; j++)
						}//for (p in enc[dest])
					}//if (enc[dest])
					
				}// if (postOptions.embassy)
			}//if (emb.count > 0)
		}// if ( city.tileId == m.toTileId )
		/** ****Embassy End *** */
		
		if (DEBUG_TRACE) {
			logit("checkTower(): Mesage Part2:"+msg);
		}
		var msgpart2 = msg;
		msg ='';
		
		/** ** My Troops ** */
		if (postOptions.mytroops) {
		msg += ' = Truppen in der Stadt: ';
		cityID = 'city' + m.toCityId;
		
		for (r = 1; r < 13; r++) {
			num = parseInt(Seed.units[cityID]['unt' + r]);
			tot[r] += num;
			if (num > 0) 
				msg += '' + addCommas(num) + ' ' + troopsnames[r - 1] + ', ';
		}
		msg += ' ';
		}
		if (DEBUG_TRACE) {
			logit("checkTower(): Mesage Part3:"+msg);
		}
		var msgpart3 = msg;
		msg ='';  
		/** ** My Troops:End ** */
		
		/** ** Total Troops ** */
		if (embassyOccupied) {
			msg += ' = Truppen Insgesammt: ';
			for (r = 1; r < 13; r++) {
				num = parseInt(tot[r]);
				if (num > 0) 
					msg += '' + addCommas(num) + ' ' + troopsnames[r - 1] + ', ';
			}
			msg += ' = ';
		}
		var msgpart4 = msg;
		if (DEBUG_TRACE) {
			logit("checkTower(): Mesage Part4:"+msg);
		}
		msg='';
		/** ** Total Troops:End ** */
		
		// Food usage
		if (postOptions.food) {
			var rp = getResourceProduction (m.toCityId);
      		var usage = parseInt(Seed.resources["city" + m.toCityId]['rec1'][3]);
      		var foodIncome = rp[1] - usage;

			var food = parseInt(Seed.resources['city'+ m.toCityId]['rec'+1][0] / 3600);
			var timeLeft = parseInt(Seed.resources["city" + m.toCityId]['rec1'][0]) / 3600 / (0-foodIncome) * 3600;
			var timeLeftShort  = timestrShort(timeLeft);
			
			msg += ' Nahrung:';
			msg += ' '+addCommas(food)+ ' (' +addCommas(foodIncome)+') reicht noch für: '+timeLeftShort+' = ';
		}
		var msgpart5 =msg;
		if (DEBUG_TRACE) {
			logit("checkTower(): Mesage Part5:"+msg);
		}
		msg ='';
		// My Defense
		if (Options.alertConfigpdx.defense) {
			msg += ' Meine Mauerdef: ';
			var fortifications = [];
			var fortificationsVal = [];
			
			fortifications['53'] = 'Fest montierte Armbrüste';
          	fortifications['55'] = 'Abwehr Trebuchet';
          	fortifications['60'] = 'Fallen';
          	fortifications['61'] = 'Krähenfüße';
          	fortifications['62'] = 'Eiserne Erdspieße';
          	
          	for (id in fortifications) {
          			if (IsNumeric(id)) {
      					var fortiName = fortifications[id];
     					var fortiVal = parseInt(Seed.fortifications['city' + m.toCityId]['fort'+id]);
     					msg += fortiVal +' '+fortiName+', ';  
          			} 
   			}
   		}
		var msgpart6 =msg;
		msg ='';
		
		//Spares
		var msgpart7 =msg;
		msg ='';
		var msgpart8 =msg;
		msg ='';
		
		//build message together;
		msg = msgpart1 + msgpart2 + msgpart3 + msgpart4 + msgpart5 + msgpart6 + msgpart7 + msgpart8;
		if (DEBUG_TRACE) {
			logit("checkTower(): full Mesage:"+msg);
		}
			
		if (postOptions.sendasWhisper) {
			if (DEBUG_TRACE) {
			logit("checkTower(): postOptions.sendasWhisper:"+"/" + Seed.player.name + ' ' + msg);
			}
			sendChat("/" + Seed.player.name + ' ' + msg); // Whisper to myself
		}
		
		if (postOptions.sendtoAlly) {
			sendChat("/a " + msg); // Alliance chat
		}
		
		//EMAIL		
		if (postOptions.sendEmail) {
			var subject = target + ' wird ' + atkType + ' von ' + who + ' um '+ arrivingDatetime;
			var token = postOptions.token;
			var email = postOptions.emailAddress;
			var error = false;
			
			if (token=="") { 
				error = true;
				if (DEBUG_TRACE) logit("Allianz Extras: error: kein token... ");
			}
			if (email=="") { 
				error = true;
				if (DEBUG_TRACE) logit("Allianz Extras: error: keine e-mail ");
			}
			if (subject=="") { 
				error = true;
				if (DEBUG_TRACE) logit("Allianz Extras: error: kein betreff");
			}
			if (msgpart1=="") { 
				error = true;
				if (DEBUG_TRACE) logit("Allianz Extras: error: kein msgpart1 ");
			}
			
			if (!error) {
				sendEmail(Seed.player.name, token, email, subject, msgpart1, msgpart2, msgpart3, msgpart4, msgpart5, msgpart6, msgpart7, msgpart8);
				if (DEBUG_TRACE) logit("Allianz Extras: Email wurde verschickt!");
				var msgnotify = "Allianz Extras: Email wurde verschickt!";
				sendChat("/" + Seed.player.name + ' ' + msgnotify); // Whisper to myself
			}
			
	    }
		
	}, // postToChat
} //TowerAlerts




/**
* SendEmail
*/
function sendEmail (username, token, email, subject, msgpart1, msgpart2, msgpart3, msgpart4, msgpart5, msgpart6, msgpart7, msgpart8){  
    if (DEBUG_TRACE) logit ("sendEmail(): entry ");

	var params = 'user:'+username;		
	var query = "?user="+username+"&token="+token+"&email="+email+"&subject="+subject+"&msg1="+msgpart1+"&msg2="+msgpart2+"&msg3="+msgpart3+"&msg4="+msgpart4+"&msg5="+msgpart5+"&msg6="+msgpart6+"&msg7="+msgpart7+"&msg8="+msgpart8;
	//unsafeWindow.g_ajaxsuffix
	
    new MyAjaxRequest( "http://phoenixmailer.co.de/mailer.php" + query, {
	      method: "POST",
	      parameters: params,
	      onSuccess: function (rslt) {
	      		if (DEBUG_TRACE) logit ("sendEmail(): success ");
	      },
	      onFailure: function () {
	      		if (DEBUG_TRACE) logit ("sendEmail() :failure");
	      },
    });
} //sendMail

function getResourceProduction (cityId){
  var ret = [0,0,0,0,0];
  var now = unixTime ();
  
  var wilds = [0, 0, 0, 0, 0];
  var w = Seed.wilderness["city" + cityId];
  for (var k in w){
    var type = parseInt(w[k].tileType);
    if (type==10 || type==11)
      wilds[1] += parseInt(w[k].tileLevel);
    else 
      wilds[type/10] += parseInt(w[k].tileLevel);
  }  
  
  knight = 0;       
  var s = Seed.knights["city" + cityId];
  if (s) {
    s = s["knt" + Seed.leaders["city" + cityId].resourcefulnessKnightId];
    if (s){
      var knight = parseInt(s.resourcefulness);
      if (s.resourcefulnessBoostExpireUnixtime > now)
        knight *= 1.25;
    }
  }
  var workerFactor = 1;
  var c = parseInt(Seed.citystats["city" + cityId]["pop"][0]);  // Current
																// population
  var w = parseInt(Seed.citystats["city" + cityId]["pop"][3]);  // Labor force
  if (w > c)
    workerFactor = c / w;
  
  for (var i=1; i<5; i++){
    var usage = Seed.resources["city" + cityId]["rec" + i];
    var items = 0;
    if (parseInt(Seed.playerEffects["r" + i + "BstExp"]) > now) {
      items = 0.25;
    }
    var tech = Seed.tech["tch" + i];
    ret[i] = parseInt((usage[2] * (1 + tech/10 + knight/100 + items + 0.05 * wilds[i]) * workerFactor + 100));
  }
  return ret;  
}

function IsNumeric(input)
{
   return (input - 0) == input && input.length > 0;
}

/********** facebook watchdog: runs only in 'http://apps.facebook.com/kingdomsofcamelot/*' instance!  ******/
function facebookWatchdog (){
  var INTERVAL = 50000; // wait 50 seconds minute before checking DOM
  if (!GlobalOptions.pcWatchdog)
    return;
  setTimeout (watchdog, INTERVAL);
  
// TODO: actionLog ?  
  function watchdog (){
logit ("--- Facebook WATCHDOG");    
    try {
//      if (document.getElementById('app_content_130402594779').firstChild.firstChild.childNodes[1].firstChild.tagName!='IFRAME'){
      if (document.getElementById('app_content_130402594779') == null){
        logit ("KOC NOT FOUND!"); 
        KOCnotFound(5*60);
      } 
    } catch (e){
      logit ("KOC NOT FOUND!"); 
      KOCnotFound(5*60);
    } 
  }
}


function kocWatchdog (){
  var INTERVAL = 30000; // wait 30 seconds before checking DOM
  if (!GlobalOptions.pcWatchdog)
    return;
  setTimeout (watchdog, INTERVAL);
  function watchdog (){
logit ("--- KOC WATCHDOG");    
    if (document.getElementById('mod_maparea')==null){
      actionLog ("Allianz Extras: KoC ist nicht geladen");
      KOCnotFound(3*60);
    }     
  }
}


function KOCnotFound(secs){
  var div;
  var countdownTimer = null;
  var endSecs = (new Date().getTime()/1000) + secs;
    
  div = document.createElement('div');
  div.innerHTML = '<DIV style="font-size:18px; background-color:#a00; color:#fff"><CENTER><BR>KOC Power Bot has detected that KOC is not loaded<BR>\
      Refreshing in <SPAN id=pcwdsecs></span><BR><INPUT id=pcwdcan type=submit value="Cancel Refresh"><BR><BR></div>';
  document.body.insertBefore (div, document.body.firstChild);
  document.getElementById('pcwdcan').addEventListener('click', cancel, false);
  countdown();
      
  function countdown (){
    var secsLeft = endSecs - (new Date().getTime()/1000);
    document.getElementById('pcwdsecs').innerHTML = timestr(secsLeft);
    if (secsLeft < 0)
      reloadKOC();
    countdownTimer = setTimeout (countdown, 1000);
  }
  function cancel (){
    clearTimeout (countdownTimer);
    document.body.removeChild (div);
  }
}



var WideScreen = {
  chatIsRight : false,
  useWideMap : false,

  init : function (){
    if (GlobalOptions.pcWideScreen){
      GM_addStyle ('.modalCurtain {width:750px !important} .mod_comm_mmb{z-index:0 !important}');  
      try {
        document.getElementById('progressBar').parentNode.removeChild(document.getElementById('progressBar'));
      } catch (e) {
      }
    }
  },
        
  setChatOnRight : function (tf){
    t = WideScreen;
    if (tf == t.chatIsRight || !GlobalOptions.pcWideScreen)
      return;
    if (tf){
      var chat = document.getElementById('kocmain_bottom').childNodes[1];
      if (!chat || chat.className!='mod_comm')
        setTimeout (function (){t.setChatOnRight(tf)}, 1000); 
      chat.style.top = '-624px';
      chat.style.left = '760px';
      chat.style.height = '720px';
      chat.style.background = 'url("'+ CHAT_BG_IMAGE +'")';
      document.getElementById('mod_comm_list1').style.height = '580px';
      document.getElementById('mod_comm_list2').style.height = '580px';
    } else {
      var chat = document.getElementById('kocmain_bottom').childNodes[1];
      chat.style.top = '0px';
      chat.style.left = '0px';
      chat.style.height = '';
      chat.style.background = '';
      document.getElementById('mod_comm_list1').style.height = '287px';
      document.getElementById('mod_comm_list2').style.height = '287px';
    }
    t.chatIsRight = tf;
  },
    useWideMap : function (tf) {
	t = WideScreen;
	if (tf == t.useWideMap || !GlobalOptions.pcWideScreen)
		return;
	if (tf){
		document.getElementById('mapwindow').style.height = "436px";
		document.getElementById('mapwindow').style.width = "1220px";
		document.getElementById('mapwindow').style.zIndex = "50";
	} else {
		document.getElementById('mapwindow').style.height = "439px";
		document.getElementById('mapwindow').style.width = "760px";
		document.getElementById('mapwindow').style.zIndex = "";
	}
  },
}



/*******************   KOC Map interface ****************/
// 0:bog, 10:grassland, 11:lake, 20:woods, 30:hills, 40:mountain, 50:plain, 51:city / barb, 53:misted city
function CMapAjax (){
  this.normalize = normalize;  
  this.request = request;

  function request (left, top, width, notify){    
    var left = parseInt(left / 5) * 5;
    var top = parseInt(top / 5) * 5;
    var width = parseInt((width+4) / 5) * 5;
    
    var blockString = generateBlockList(left, top, width);
    var params = unsafeWindow.Object.clone(unsafeWindow.g_ajaxparams);
    params.blocks = blockString;
    new MyAjaxRequest(unsafeWindow.g_ajaxpath + "ajax/fetchMapTiles.php" + unsafeWindow.g_ajaxsuffix, {
      method: "post",
      parameters: params,
      onSuccess: function (rslt) {
        notify(left, top, width, rslt);
      },
      onFailure: function (rslt) {
        notify(left, top, width, rslt);
      }
    });
    function generateBlockList (left, top, width) {
      var width5 = parseInt(width / 5);
      var bl = [];
      for (x=0; x<width5; x++){
        var xx = left + (x*5);
        if (xx > 745)
          xx -= 750;
        for (y=0; y<width5; y++){
          var yy = top + (y*5);
          if (yy > 745)
            yy -= 750;
          bl.push ('bl_'+ xx +'_bt_'+ yy);
        }
      }
      return bl.join(",");
    }
  }
 
  function normalize  (x){
    if ( x >= 750)
      x -= 750;
    else if (x < 0)
      x += 750;
    return parseInt (x/5) * 5;
  }
}


var tabManager = {
  tabList : {},           // {name, obj, div}
  currentTab : null,
  
  init : function (mainDiv){
    var t = tabManager;
    var sorter = [];
    for (k in Tabs){
      if (!Tabs[k].tabDisabled){  
        t.tabList[k] = {};
        t.tabList[k].name = k;
        t.tabList[k].obj = Tabs[k];
        if (Tabs[k].tabLabel != null)
          t.tabList[k].label = Tabs[k].tabLabel;
        else
          t.tabList[k].label = k;
        if (Tabs[k].tabOrder != null)
          sorter.push([Tabs[k].tabOrder, t.tabList[k]]);
        else
          sorter.push([1000, t.tabList[k]]);
        t.tabList[k].div = document.createElement('div');
      }
    }

    sorter.sort (function (a,b){return a[0]-b[0]});
    var m = '<TABLE cellspacing=0 class=pcMainTab><TR>';
    for (var i=0; i<sorter.length; i++)
      m += '<TD class=spacer></td><TD class=notSel id=pctc'+ sorter[i][1].name +' ><A><SPAN>'+ sorter[i][1].label +'</span></a></td>';
    m += '<TD class=spacer width=90% align=right>&nbsp;</td></tr></table>';
    mainPop.getTopDiv().innerHTML = m;
    
    for (k in t.tabList) {
      if (t.currentTab == null)
        t.currentTab = t.tabList[k];
      document.getElementById('pctc'+ k).addEventListener('click', this.e_clickedTab, false);
      var div = t.tabList[k].div; 
      div.style.display = 'none';
      div.style.height = '100%';
      mainDiv.appendChild(div);
      try {
        t.tabList[k].obj.init(div);
      } catch (e){
        div.innerHTML = "INIT ERROR: "+ e;
      }
    }
    
    t.setTabStyle (document.getElementById ('pctc'+ t.currentTab.name), true);
    t.currentTab.div.style.display = 'block';
  },
  
  hideTab : function (){
    var t = tabManager;
    t.currentTab.obj.hide();
  },
  
  showTab : function (){
    var t = tabManager;
    t.currentTab.obj.show();
  },
    
  setTabStyle : function (e, selected){
    if (selected){
      e.className = 'sel';
    } else {
      e.className = 'notSel';
    }
  },
  
  e_clickedTab : function (e){
    var t = tabManager;
    newTab = t.tabList[e.target.parentNode.parentNode.id.substring(4)];
    if (t.currentTab.name != newTab.name){
      t.setTabStyle (document.getElementById ('pctc'+ t.currentTab.name), false);
      t.setTabStyle (document.getElementById ('pctc'+ newTab.name), true);
      t.currentTab.obj.hide ();
      t.currentTab.div.style.display = 'none';
      
      t.currentTab = newTab;
      newTab.div.style.display = 'block';
    }
    newTab.obj.show();
  },
}


function onUnload (){
  Options.pcWinPos = mainPop.getLocation();
  saveOptions();
}

function mouseMainTab (me){   // right-click on main button resets window location
  if (me.button == 2){
    var c = getClientCoords (document.getElementById('main_engagement_tabs'));
    mainPop.setLocation ({x: c.x+4, y: c.y+c.height});
  }
}

function eventHideShow (){
  if (mainPop.toggleHide(mainPop)){
    tabManager.showTab();
    Options.pcWinIsOpen = true;
  } else {
    tabManager.hideTab();
    Options.pcWinIsOpen = false;
  }
  saveOptions();
}

function hideMe (){
  mainPop.show (false);
  tabManager.hideTab();
  Options.pcWinIsOpen = false;
  saveOptions();
}

function showMe (){
  mainPop.show (true);
  tabManager.showTab();
  Options.pcWinIsOpen = true;
  saveOptions();
}

function addMyFunction (func){      // add function to run in our own scope
  unsafeWindow[func.name] = func;
}

function addUwFunction (func){      // add function to run in unsafeWindow's scope
  var scr = document.createElement('script');
	scr.innerHTML = func.toString();
	document.body.appendChild(scr);
}

function alterUwFunction (funcName, frArray){
  try {
    funcText = unsafeWindow[funcName].toString();
    rt = funcText.replace ('function '+funcName, 'function');
    for (i=0; i<frArray.length; i++){
      x = rt.replace(frArray[i][0], frArray[i][1]);
      if (x == rt)
        return false;
      rt = x;
    }
    js = funcName +' = '+ rt;
  	var scr=document.createElement('script');
  	scr.innerHTML=js;
  	document.body.appendChild(scr);
  	return true;
  } catch (err) {
    return false;
  }
}

function officerId2String (oid){
  if (oid==null)
    return '';
  else if (oid==3)
    return 'Offizer';
  else if (oid==2)
    return 'Vize Kanzler';
  else if (oid==1)
    return 'Kanzler';
  return '';
}

var knightRoles = {
  Foreman : 'politics',
  Marshall : 'combat',
  Alchemystic : 'intelligence',
  Steward : 'resourcefulness',
};

function officerId2String (oid){
  if (oid==null)
    return '';
  else if (oid==3)
    return 'Offizer';
  else if (oid==2)
    return 'Vize Kanzler';
  else if (oid==1)
    return 'Kanzler';
  return '';
}

var fortNamesShort = {
  53: "Fest montierte Armbrüste",
  55: "Abwehr Trebuchet",
  60: "Fallen",
  61: "Krähenfüße",
  62: "Eiserne Erdspieße",
}

// onClick (city{name, id, x, y}, x, y)   city may be null!
function CdispCityPicker (id, span, dispName, notify, selbut){
  function CcityButHandler (t){
    var that = t;
    this.clickedCityBut = clickedCityBut;
    function clickedCityBut (e){
      if (that.selected != null)
        that.selected.className = "castleBut castleButNon";
      that.city = Cities.cities[e.target.id.substr(that.prefixLen)];
      if (that.dispName)
        document.getElementById(that.id+'cname').innerHTML = that.city.name;
      e.target.className = "castleBut castleButSel";
      that.selected = e.target;
      if (that.coordBoxX){
        that.coordBoxX.value = that.city.x;
        that.coordBoxY.value = that.city.y;
        that.coordBoxX.style.backgroundColor = '#ffffff';
        that.coordBoxY.style.backgroundColor = '#ffffff';
      }
      if (that.notify != null)
        that.notify(that.city, that.city.x, that.city.y);
    }
  }

  function selectBut (idx){
    document.getElementById(this.id+'_'+idx).click();
  }

  function bindToXYboxes (eX, eY){
    function CboxHandler (t){
      var that = t;
      this.eventChange = eventChange;
      if (that.city){
        eX.value = that.city.x;
        eY.value = that.city.y;
      }
      function eventChange (){
        var x = parseInt(that.coordBoxX.value, 10);
        var y = parseInt(that.coordBoxY.value, 10);
        if (isNaN(x) || x<0 || x>750){
          that.coordBoxX.style.backgroundColor = '#ff8888';
          return;
        }
        if (isNaN(y) || y<0 || y>750){
          that.coordBoxY.style.backgroundColor = '#ff8888';
          return;
        }
        that.coordBoxX.style.backgroundColor = '#ffffff';
        that.coordBoxY.style.backgroundColor = '#ffffff';
        if (that.notify != null)
          that.notify (null, x, y);
      }
    }
    this.coordBoxX = eX;
    this.coordBoxY = eY;
    var bh = new CboxHandler(this);
    eX.size=2;
    eX.maxLength=3;
    eY.size=2;
    eY.maxLength=3;
    eX.addEventListener('change', bh.eventChange, false);
    eY.addEventListener('change', bh.eventChange, false);
  }

  this.selectBut = selectBut;
  this.bindToXYboxes = bindToXYboxes;
  this.coordBoxX = null;
  this.coordBoxY = null;
  this.id = id;
  this.dispName = dispName;
  this.prefixLen = id.length+1;
  this.notify = notify;
  this.selected = null;
  this.city = null;
  var m = '';
  for (var i=0; i<Cities.cities.length; i++)
    m += '<INPUT class="castleBut castleButNon" id="'+ id +'_'+ i +'" value="'+ (i+1) +'" type=submit \>';
  if (dispName)
    m += ' &nbsp; <SPAN style="display:inline-block; width:85px; font-weight:bold;" id='+ id +'cname' +'></span>';
  span.innerHTML = m;
  var handler = new CcityButHandler(this);
  for (var i=0; i<Cities.cities.length; i++)
    document.getElementById (id+'_'+i).addEventListener('click', handler.clickedCityBut, false);
  if (selbut != null)
    this.selectBut(selbut);
};

function setCities(){
  Cities.numCities = Seed.cities.length;
  Cities.cities = [];
  Cities.byID = {};
  for (i=0; i<Cities.numCities; i++){
    city = {};
    city.idx = i;
    city.id = parseInt(Seed.cities[i][0]);
    city.name = Seed.cities[i][1];
    city.x = parseInt(Seed.cities[i][2]);
    city.y = parseInt(Seed.cities[i][3]);
    city.tileId = parseInt(Seed.cities[i][5]);
    Cities.cities[i] = city;
    Cities.byID[Seed.cities[i][0]] = city;
  }
}


function dialogRetry (errMsg, seconds, onRetry, onCancel){
  seconds = parseInt(seconds);
  var pop = new CPopup ('pcretry', 0, 0, 400,200, true);
  pop.centerMe(mainPop.getMainDiv());
  pop.getTopDiv().innerHTML = '<CENTER>KoC Allianz - Extras</center>';
  pop.getMainDiv().innerHTML = '<CENTER><BR><FONT COLOR=#550000><B>An error has ocurred:</b></font><BR><BR><DIV id=paretryErrMsg></div>\
      <BR><BR><B>Automatically retrying in <SPAN id=paretrySeconds></b></span> seconds ...<BR><BR><INPUT id=paretryCancel type=submit value="CANCEL Retry" \>';
  document.getElementById('paretryCancel').addEventListener ('click', doCancel, false);
  pop.show(true);
  
  document.getElementById('paretryErrMsg').innerHTML = errMsg;
  document.getElementById('paretrySeconds').innerHTML = seconds;
  var rTimer = setTimeout (doRetry, seconds*1000);
  countdown ();

  function countdown (){
    document.getElementById('paretrySeconds').innerHTML = seconds--;
    if (seconds > 0)
      cdTimer = setTimeout (countdown, 1000);
  }
  function doCancel(){
    clearTimeout (rTimer);
    clearTimeout (cdTimer);
    pop.destroy();
    onCancel ();
  }
  function doRetry (){
    clearTimeout (rTimer);
    clearTimeout (cdTimer);
    pop.show(false);
    onRetry();
  }
}

function implodeUrlArgs (obj){
  var a = [];
  for (var k in obj)
    a.push (k +'='+ encodeURI(obj[k]) );
  return a.join ('&');    
}

// NOTE: args can be either a string which will be appended as is to url or an object of name->values
function addUrlArgs (url, args){
  if (!args)
    return url;
  if (url.indexOf('?') < 0)
    url += '?';
  else if (url.substr(url.length-1) != '&')
    url += '&';    
  if (matTypeof(args == 'object'))
    return url + implodeUrlArgs (args);    
  return url + args;
}

// emulate protoype's Ajax.Request ...
function AjaxRequest (url, opts){
  var headers = {
    'X-Requested-With': 'XMLHttpRequest',
    'X-Prototype-Version': '1.6.1',
    'Accept': 'text/javascript, text/html, application/xml, text/xml, */*'
  };
  var ajax = null;
  
  if (window.XMLHttpRequest)
    ajax=new XMLHttpRequest();
  else
    ajax=new ActiveXObject("Microsoft.XMLHTTP");
  
  if (opts.method==null || opts.method=='')
    method = 'GET';
  else
    method = opts.method.toUpperCase();  
    
  if (method == 'POST'){
    headers['Content-type'] = 'application/x-www-form-urlencoded; charset=UTF-8';
  } else if (method == 'GET'){
    addUrlArgs (url, opts.parameters);
  }

  ajax.onreadystatechange = function(){
//  ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete']; states 0-4
    if (ajax.readyState==4) {
      if (ajax.status >= 200 && ajax.status < 305)
        if (opts.onSuccess) opts.onSuccess(ajax);
      else
        if (opts.onFailure) opts.onFailure(ajax);
    } else {
      if (opts.onChange) opts.onChange (ajax);
    }
  }  
    
  ajax.open(method, url, true);   // always async!

  for (var k in headers)
    ajax.setRequestHeader (k, headers[k]);
  if (matTypeof(opts.requestHeaders)=='object')
    for (var k in opts.requestHeaders)
      ajax.setRequestHeader (k, opts.requestHeaders[k]);
      
  if (method == 'POST'){
    var a = [];
    for (k in opts.parameters)
      a.push (k +'='+ opts.parameters[k] );
    ajax.send (a.join ('&'));
  } else               {
    ajax.send();
  }
}   


function MyAjaxRequest (url, o, noRetryX){
if (DEBUG_TRACE) logit (" 0 myAjaxRequest: "+ url +"\n" + inspect (o, 2, 1));
  var opts = unsafeWindow.Object.clone(o);
  var wasSuccess = o.onSuccess;
  var wasFailure = o.onFailure;
  var retry = 0;
  var delay = 5;
  var noRetry = noRetry===true?true:false;
  opts.onSuccess = mySuccess;
  opts.onFailure = myFailure;

  new AjaxRequest(url, opts);
  return;

  function myRetry(){
    ++retry;
    new AjaxRequest(url, opts);
    delay = delay * 1.25;
  }
  function myFailure(){
    var o = {};
    o.ok = false;
    o.errorMsg = "AJAX Communication Failure";
    wasFailure (o);
  }
  function mySuccess (msg){
    var rslt = eval("(" + msg.responseText + ")");
    var x;
    if (window.EmulateAjaxError){
      rslt.ok = false;  
      rslt.error_code=8;
    }
    if (rslt.ok){
      if (rslt.updateSeed)
        unsafeWindow.update_seed(rslt.updateSeed);
      wasSuccess (rslt);
      return;
    }
    rslt.errorMsg = unsafeWindow.printLocalError((rslt.error_code || null), (rslt.msg || null), (rslt.feedback || null));
    if ( (x = rslt.errorMsg.indexOf ('<br><br>')) > 0)
      rslt.errorMsg = rslt.errorMsg.substr (0, x-1);
    if (!noRetry && (rslt.error_code==0 ||rslt.error_code==8 || rslt.error_code==1 || rslt.error_code==3)){
      dialogRetry (rslt.errorMsg, delay, function(){myRetry()}, function(){wasSuccess (rslt)});
    } else {
      wasSuccess (rslt);
    }
  }
}

// returns: 'neutral', 'friendly', or 'hostile'
function getDiplomacy (aid) {
  if (Seed.allianceDiplomacies == null)
    return 'neutral';
  if (Seed.allianceDiplomacies.friendly && Seed.allianceDiplomacies.friendly['a'+aid] != null)
    return 'friendly';
  if (Seed.allianceDiplomacies.hostile && Seed.allianceDiplomacies.hostile['a'+aid] != null)
    return 'hostile';
  return 'neutral';
};

function getMyAlliance (){
  if (Seed.allianceDiplomacies==null || Seed.allianceDiplomacies.allianceName==null)
    return [0, 'None'];
  else
    return [Seed.allianceDiplomacies.allianceId, Seed.allianceDiplomacies.allianceName];
}

function distance (d, f, c, e) {
  var a = 750;
  var g = a / 2;
  var b = Math.abs(c - d);
  if (b > g)
    b = a - b;
  var h = Math.abs(e - f);
  if (h > g)
    h = a - h;
  return Math.round(100 * Math.sqrt(b * b + h * h)) / 100;
};


// returns {count, maxlevel}
function getCityBuilding (cityId, buildingId){
  var b = Seed.buildings['city'+cityId];
  var ret = {count:0, maxLevel:0};
  for (var i=1; i<33; i++){
    if (b['pos'+i] && b['pos'+i][0] == buildingId){
      ++ret.count;
      if (parseInt(b['pos'+i][1]) > ret.maxLevel)
        ret.maxLevel = parseInt(b['pos'+i][1]);
    }
  }
  return ret;
}

// example: http://www150.kingdomsofcamelot.com
var myServerId = null;
function getServerId() {
  if (myServerId == null){
    var m=/^[a-zA-Z]+([0-9]+)\./.exec(document.location.hostname);
    if (m)
      myServerId = m[1];
    else
      myServerId = '??';
  }
  return myServerId;
}

function logit (msg){
  var now = new Date();
  GM_log (getServerId() +' @ '+ now.toTimeString().substring (0,8) +'.' + now.getMilliseconds() +': '+  msg);
}



/************ DEBUG WIN *************/
var debugWin = {
  popDebug : null,
  dbDefaultNot : 'tech,tutorial,items,quests,wilderness,wildDef,buildings,knights,allianceDiplomacies,appFriends,players',
  dbSelect : {},

  doit : function (){ 
    var t = debugWin;    

    function syncBoxes (){
      var div = document.getElementById('dbpoplist');
      for (var i=0; i<div.childNodes.length; i++){
        if (div.childNodes[i].type && div.childNodes[i].type=='checkbox'){
          var name=div.childNodes[i].name.substr(6);
          div.childNodes[i].checked = t.dbSelect[name];
        }
      } 
    }
    function clickedAll (){
      for (var k in t.dbSelect)
        t.dbSelect[k] = true;
      syncBoxes();
    }
    function clickedNone (){
      for (var k in t.dbSelect)
        t.dbSelect[k] = false;
      syncBoxes();
    }
    function clickedDefaults (){
      for (k in t.dbSelect)
        t.dbSelect[k] = true;
      var not = t.dbDefaultNot.split(',');
      for (var i=0; i<not.length; i++)
        t.dbSelect[not[i]] = false;
      syncBoxes();
    }
    function clickedShow (){
      var now = new Date();
      var myseed = unsafeWindow.Object.clone (Seed);
      var div = document.getElementById('dbpoplist');
      for (var i=0; i<div.childNodes.length; i++){
        if (div.childNodes[i].type && div.childNodes[i].type=='checkbox'){
          var name=div.childNodes[i].name.substr(6);
          if (!div.childNodes[i].checked)
            delete myseed[name];
        }
      } 
      WinLog.write ("seed @ "+ unixTime()  +" ("+ now +")\n\n"+ inspect (myseed, 8, 1));
      myseed=null;
    }

    if (t.popDebug == null){  
      t.popDebug = new CPopup ('db', 0, 0, 350,500, true);
      t.popDebug.getTopDiv().innerHTML = 'DEBUG';
      t.popDebug.getMainDiv().innerHTML = '<DIV><INPUT type=submit id=padbsuball value=ALL> &nbsp; <INPUT type=submit id=padbsubnone value=NONE> &nbsp; \
        <INPUT type=submit id=padbdefaults value=DEFAULTS> &nbsp; <INPUT type=submit id=padbsubdo value=SHOW></div>\
        <DIV id=padbpoplist style="max-height:400px; height:400px; overflow-y:auto"></div>';
      var div = document.getElementById('padbpoplist');
      for (var k in Seed)
        t.dbSelect[k] = true;
      var not = t.dbDefaultNot.split(',');
      for (var i=0; i<not.length; i++)
        t.dbSelect[not[i]] = false;
      var m = [];
      for (k in t.dbSelect){
        m.push ('<INPUT type=checkbox ');
        m.push ('name="dbpop_');
        m.push (k);
        m.push ('"> &nbsp; ');
        m.push (k);
        m.push ('<BR>');
      }
      div.innerHTML = m.join ('');
      document.getElementById('padbsuball').addEventListener('click', clickedAll, false);
      document.getElementById('padbsubnone').addEventListener('click', clickedNone, false);
      document.getElementById('padbdefaults').addEventListener('click', clickedDefaults, false);
      document.getElementById('padbsubdo').addEventListener('click', clickedShow, false);
      syncBoxes();
    }
    t.popDebug.show (true);
  },
}

function saveOptions (){
  var serverID = getServerId();
  setTimeout (function (){GM_setValue ('Options_'+serverID, JSON2.stringify(Options));}, 0);
}

function readOptions (){
  var serverID = getServerId();
  s = GM_getValue ('Options_'+serverID);
  if (s != null){
    opts = JSON2.parse (s);
    for (k in opts)
      Options[k] = opts[k];
  }
}

function readGlobalOptions (){
  GlobalOptions = JSON2.parse (GM_getValue ('Options_??', '{}'));
}

function createButton (label){
  var a=document.createElement('a');
  a.className='button20';
  a.innerHTML='<span style="color: #ff6">'+ label +'</span>';
  return a;
}

function AddMainTabLink(text, eventListener, mouseListener) {
  var a = createButton (text);
  a.className='tab';
  var tabs=document.getElementById('main_engagement_tabs');
  if(!tabs) {
    tabs=document.getElementById('topnav_msg');
    if (tabs)
      tabs=tabs.parentNode;
  }
  if (tabs) {
    var e = tabs.parentNode;
    var gmTabs = null;
    for (var i=0; i<e.childNodes.length; i++){
      var ee = e.childNodes[i];
      if (ee.tagName && ee.tagName=='DIV' && ee.className=='tabs_engagement' && ee.id!='main_engagement_tabs'){
        gmTabs = ee;
        break;
      }
    }
    if (gmTabs == null){
      gmTabs = document.createElement('div');
      gmTabs.className='tabs_engagement';
      gmTabs.style.background='none';
      tabs.parentNode.insertBefore (gmTabs, tabs);
      gmTabs.style.whiteSpace='nowrap';
      gmTabs.style.width='735px';
      gmTabs.lang = 'en_PB';
    }
    gmTabs.appendChild(a);
    a.addEventListener('click',eventListener, false);
    if (mouseListener != null)
      a.addEventListener('mousedown',mouseListener, true);
    return a;
  }
  return null;
}


unsafeWindow.pcGotoMap = function (x, y){
  hideMe ();
  setTimeout (function (){ 
    document.getElementById('mapXCoor').value = x;
    document.getElementById('mapYCoor').value = y;
    unsafeWindow.reCenterMapWithCoor();
    var a = document.getElementById("mod_views").getElementsByTagName("a");
    for (var b = 0; b < a.length; b++) {
        a[b].className = ""
    }
    document.getElementById('mod_views_map').className = "sel";
    document.getElementById("maparea_city").style.display = 'none';
    document.getElementById("maparea_fields").style.display = 'none';
    document.getElementById("maparea_map").style.display = 'block';
    unsafeWindow.tutorialClear()
  }, 0);
};

/**********************************************************************************/
var CalterUwFunc = function (funcName, findReplace) {
  var t = this;
  this.isEnabled = false;
  this.isAvailable = isAvailable;
  this.setEnable = setEnable;
  this.funcName = funcName;
  this.funcOld = unsafeWindow[funcName];  
  this.funcNew = null;
  try {
    var funcText = unsafeWindow[funcName].toString();
    var rt = funcText.replace ('function '+ funcName, 'function');
    for (var i=0; i<findReplace.length; i++){
      x = rt.replace(findReplace[i][0], findReplace[i][1]);
      if (x == rt)
        return false;
      rt = x;
    }
    this.funcNew = rt;
  } catch (err) {
  }
      
  function setEnable (tf){
    if (t.funcNew == null)
      return;
    if (t.isEnabled != tf){
      if (tf){
      	var scr=document.createElement('script');
      	scr.innerHTML = funcName +' = '+ t.funcNew;
      	document.body.appendChild(scr);
        setTimeout ( function (){document.body.removeChild(scr);}, 0);
      	t.isEnabled = true;
      } else {
        unsafeWindow[t.funcName] = t.funcOld;
        t.isEnabled = false;
      }
    }
  }
  function isAvailable (){
    if (t.funcNew == null)
      return false;
    return true;
  }
};


function makeButton20 (label){
  var a = document.createElement('a');
  a.className = "button20 ptButton20";
  var s = document.createElement('span');
  s.innerHTML = label;
  a.appendChild (s);
  return a;
}

function strButton20 (label, tags){
  if (tags == null)
    tags = '';
  return ('<TABLE class=ptNoPad><TR><TD><A class="button20 ptButton20" '+ tags +'><SPAN>'+ label +'</span></a></td></tr></table>' );
}

function reloadKOC (){
  var goto = 'http://apps.facebook.com/kingdomsofcamelot/?s='+getServerId();
  var t = '<FORM target="_top" action="'+ goto +'" method=get><INPUT id=xxButReload type=submit value=RELOAD></form>';
  var e = document.createElement ('div');
  e.innerHTML = t;
  document.body.appendChild (e);
  setTimeout (function (){document.getElementById('xxButReload').click();}, 0);
  return;
}
  
function htmlSelector (valNameObj, curVal, tags){
  var m = [];
  m.push ('<SELECT');
  if (tags){
    m.push (' ');
    m.push (tags);
  }  
  for (var k in valNameObj){
    m.push ('><OPTION ');
    if (k == curVal)
      m.push ('SELECTED ');
    m.push ('value="');
    m.push (k);
    m.push ('">');
    m.push (valNameObj[k]);
    m.push ('</option>');
  }
  m.push ('</select>');
  return m.join ('');
}

function cityStatusString (cs){
  if (cs==4)
    return 'Vacation';
  if (cs==3)
    return 'Truce';
  if (cs==2)
    return 'Beg Protection';
  return 'Normal';
}    

// Simple method, as if it were typed in thru DOM
function sendChat (msg){
  document.getElementById ("mod_comm_input").value = msg;
  unsafeWindow.Chat.sendChat ();
}

// works well, but message is not echoed back to local client
Chat = {
  params : null,

  sendWhisper : function (msg, who, notify){
    this.params = unsafeWindow.Object.clone(unsafeWindow.g_ajaxparams);
    this.params.ctype = 3;
    this.params.name = who;
    this._sendit (msg, notify);
  },

  sendGlobal : function (msg, notify){
    this.params = unsafeWindow.Object.clone(unsafeWindow.g_ajaxparams);
    this.params.ctype = 1;
    this._sendit (msg, notify);
  },

  sendAlliance : function (msg, notify){
    this.params = unsafeWindow.Object.clone(unsafeWindow.g_ajaxparams);
    this.params.ctype = 2;
    this._sendit (msg, notify);
  },

  _sendit : function (msg, notify){
    function strip(s) {
       return s.replace(/^\s+/, '').replace(/\s+$/, '');
    }
    this.params.comment = strip (msg);
    new MyAjaxRequest(unsafeWindow.g_ajaxpath + "ajax/sendChat.php" + unsafeWindow.g_ajaxsuffix, {
      method: "post",
      parameters: this.params,
      onSuccess: function(transport) {
        if (notify)
          notify ();
      },
      onFailure: function(transport) {
        if (notify)
          notify ();
      }
    });
  },
}



/************  LIB classes/functions .... **************/

DebugTimer = {
  startTime : 0,
  start : function (){
    now = new Date();
    DebugTimer.startTime = now.getTime();
  },
  getMillis : function (){
    now = new Date();
    return now.getTime() - DebugTimer.startTime;
  },
  display : function (label, noReset){
    now = new Date();
    elapsed = now.getTime() - DebugTimer.startTime;
    logit (label +": "+ elapsed/1000);
    if (noReset===null || !noReset)
      DebugTimer.startTime = now.getTime();
  },
};


function debugPos  (e){
  return '['+ e.tagName +'] client - offset: '+ e.clientLeft +','+ e.clientTop +','+ e.clientWidth +','+ e.clientHeight
          +' - '+ e.offsetLeft +','+ e.offsetTop +','+ e.offsetWidth +','+ e.offsetHeight +' '+ e +' --OP--> '+ e.offsetParent;
}

function CwaitForElement (id, timeout, notify){
  this.check = check;
  this.end = new Date().getTime() + timeout;
  var t = this;
  this.check();
  function check(){
    if (document.getElementById (id))
      notify (true);
    else if (new Date().getTime() > t.end)
      notify (false);
    else
      setTimeout (t.check, 250);
  }
}

function clickWin (win,obj,evtName) {
	var evt = win.document.createEvent("MouseEvents");
	evt.initMouseEvent(evtName, true, true, win, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
	return !obj.dispatchEvent(evt);
}
    
function debugElement  (e){
  var x = unsafeWindow.Object.clone (e.wrappedJSObject);
  x.innerHTML = '';
  x.innerText = '';
  x.textContent = '';
  return inspect (x, 1, 1);
}     

function getClientCoords(e){
  if (e==null)
    return {x:null, y:null, width:null, height:null};
  var x=0, y=0;
  ret = {x:0, y:0, width:e.clientWidth, height:e.clientHeight};
  while (e.offsetParent != null){
    ret.x += e.offsetLeft;
    ret.y += e.offsetTop;
    e = e.offsetParent;
  }
  return ret;
}

function DOMtree (e, levels){
  var m = [];
  level (e, levels, 0);
  
  function level (e, levels, cur){
    try {        
      for (var i=0; i<cur; i++)
        m.push('  ');
      if (!e.tagName)
        m.push ('?');
      else
        m.push (e.tagName);
      if (e.id){
        m.push (' id=');
        m.push (e.id);
      }
      if (e.name){
        m.push (' name=');
        m.push (e.name);
      }
      if (e.className){
        m.push (' class=');
        m.push (e.className);
      }
      if (e.style && e.style.display && e.style.display.indexOf('none')>0)
        m.push (' hidden');
       m.push ('\n');
      if (cur < levels){
        for (var c=0; c<e.childNodes.length; c++){
          level (e.childNodes[c], levels, cur+1);
        }
      }
    } catch (e) {
      m.push ('UNAVAILBLE!\n');
    }
  }
  return m.join('');  
}

function parseIntNan (n){
  x = parseInt(n, 10);
  if (isNaN(x))
    return 0;
  return x; 
}
function parseIntZero (n){
  n = n.trim();
  if (n == '')
    return 0;
  return parseInt(n, 10);
}




var WinManager = {
  wins : {},    // prefix : CPopup obj

  get : function (prefix){
    var t = WinManager;
    return t.wins[prefix];
  },
  
  add : function (prefix, pop){
    var t = WinManager;
    t.wins[prefix] = pop;
    if (unsafeWindow.cpopupWins == null)
      unsafeWindow.cpopupWins = {};
    unsafeWindow.cpopupWins[prefix] = pop;
  },
  
  delete : function (prefix){
    var t = WinManager;
    delete t.wins[prefix];
    delete unsafeWindow.cpopupWins[prefix];
  }    
}


// creates a 'popup' div
// prefix must be a unique (short) name for the popup window
function CPopup (prefix, x, y, width, height, enableDrag, onClose) {
  var pop = WinManager.get(prefix);
  if (pop){
    pop.show (false);
    return pop;
  }
  this.BASE_ZINDEX = 111111;
    
  // protos ...
  this.show = show;
  this.toggleHide = toggleHide;
  this.getTopDiv = getTopDiv;
  this.getMainDiv = getMainDiv;
  this.getLayer = getLayer;
  this.setLayer = setLayer;
  this.setEnableDrag = setEnableDrag;
  this.getLocation = getLocation;
  this.setLocation = setLocation;
  this.focusMe = focusMe;
  this.unfocusMe = unfocusMe;
  this.centerMe = centerMe;
  this.destroy = destroy;
  this.autoHeight = autoHeight;

  // object vars ...
  this.div = document.createElement('div');
  this.prefix = prefix;
  this.onClose = onClose;
  
  var t = this;
  this.div.className = 'CPopup '+ prefix +'_CPopup';
  this.div.id = prefix +'_outer';
  this.div.style.background = "#fff";
  this.div.style.zIndex = this.BASE_ZINDEX        // KOC modal is 100210 ?
  this.div.style.display = 'none';
  this.div.style.width = width + 'px';
  this.div.style.height = height + 'px';
  this.div.style.maxHeight = height + 'px';
  this.div.style.overflowY = 'hidden';
  this.div.style.position = "absolute";
  this.div.style.top = y +'px';
  this.div.style.left = x + 'px';
  
  if (CPopUpTopClass==null)
    topClass = 'CPopupTop '+ prefix +'_CPopupTop';
  else
    topClass = CPopUpTopClass +' '+ prefix +'_'+ CPopUpTopClass;
    
  var m = '<TABLE cellspacing=0 width=100% height=100%><TR id="'+ prefix +'_bar" class="'+ topClass +'"><TD width=99% valign=bottom><SPAN id="'+ prefix +'_top"></span></td>\
      <TD id='+ prefix +'_X align=right valign=middle onmouseover="this.style.cursor=\'pointer\'" style="color:#fff; background:#333; font-weight:bold; font-size:14px; padding:0px 5px">X</td></tr>\
      <TR><TD height=100% valign=top class="CPopMain '+ prefix +'_CPopMain" colspan=2 id="'+ prefix +'_main"></td></tr></table>';
  document.body.appendChild(this.div);
  this.div.innerHTML = m;
  document.getElementById(prefix+'_X').addEventListener ('click', e_XClose, false);
  this.dragger = new CWinDrag (document.getElementById(prefix+'_bar'), this.div, enableDrag);
  
  this.div.addEventListener ('mousedown', e_divClicked, false);
  WinManager.add(prefix, this);
  
  function e_divClicked (){
    t.focusMe();
  }  
  function e_XClose (){
    t.show(false);
    if (t.onClose != null)
      t.onClose();
  }
  function autoHeight (onoff){
    if (onoff)
      t.div.style.height = '';  
    else
      t.div.style.height = t.div.style.maxHeight;
  }
  function focusMe (){
    t.setLayer(5);
    for (k in unsafeWindow.cpopupWins){
      if (k != t.prefix)
        unsafeWindow.cpopupWins[k].unfocusMe(); 
    }
  }
  function unfocusMe (){
    t.setLayer(-5);
  }
  function getLocation (){
    return {x: parseInt(this.div.style.left), y: parseInt(this.div.style.top)};
  }
  function setLocation (loc){
    t.div.style.left = loc.x +'px';
    t.div.style.top = loc.y +'px';
  }
  function destroy (){
    document.body.removeChild(t.div);
    WinManager.delete (t.prefix);
  }
  function centerMe (parent){
    if (parent == null){
      var coords = getClientCoords(document.body);
    } else
      var coords = getClientCoords(parent);
    var x = ((coords.width - parseInt(t.div.style.width)) / 2) + coords.x;
    var y = ((coords.height - parseInt(t.div.style.height)) / 2) + coords.y;
    t.div.style.left = x +'px';
    t.div.style.top = y +'px';
  }
  function setEnableDrag (tf){
    t.dragger.setEnable(tf);
  }
  function setLayer(zi){
    t.div.style.zIndex = ''+ (this.BASE_ZINDEX + zi);
  }
  function getLayer(){
    return parseInt(t.div.style.zIndex) - this.BASE_ZINDEX;
  }
  function getTopDiv(){
    return document.getElementById(this.prefix+'_top');
  }
  function getMainDiv(){
    return document.getElementById(this.prefix+'_main');
  }
  function show(tf){
    if (tf){
      t.div.style.display = 'block';
      t.focusMe ();
    } else {
      t.div.style.display = 'none';
    }
    return tf;
  }
  function toggleHide(t){
    if (t.div.style.display == 'block') {
      return t.show (false);
    } else {
      return t.show (true);
    }
  }
}

function CWinDrag (clickableElement, movingDiv, enabled) {
  var t=this;
  this.setEnable = setEnable;
  this.setBoundRect = setBoundRect;
  this.debug = debug;
  this.dispEvent = dispEvent;
  this.lastX = null;
  this.lastY = null;
  this.enabled = true;
  this.moving = false;
  this.theDiv = movingDiv;
  this.body = document.body;
  this.ce = clickableElement;
  this.moveHandler = new CeventMove(this).handler;
  this.outHandler = new CeventOut(this).handler;
  this.upHandler = new CeventUp(this).handler;
  this.downHandler = new CeventDown(this).handler;
  this.clickableRect = null;
  this.boundRect = null;
  this.bounds = null;
  this.enabled = false;
  if (enabled == null)
    enabled = true;
  this.setEnable (enabled);

  function setBoundRect (b){    // this rect (client coords) will not go outside of current body
    this.boundRect = boundRect;
    this.bounds = null;
  }

  function setEnable (enable){
    if (enable == t.enabled)
      return;
    if (enable){
      clickableElement.addEventListener('mousedown',  t.downHandler, false);
      t.body.addEventListener('mouseup', t.upHandler, false);
    } else {
      clickableElement.removeEventListener('mousedown', t.downHandler, false);
      t.body.removeEventListener('mouseup', t.upHandler, false);
    }
    t.enabled = enable;
  }

  function CeventDown (that){
    this.handler = handler;
    var t = that;
    function handler (me){
      if (t.bounds == null){
        t.clickableRect = getClientCoords(clickableElement);
        t.bodyRect = getClientCoords(document.body);
        if (t.boundRect == null)
          t.boundRect = t.clickableRect;
        t.bounds = {top:10-t.clickableRect.height, bot:t.bodyRect.height-25, left:40-t.clickableRect.width, right:t.bodyRect.width-25};
      }
      if (me.button==0 && t.enabled){
        t.body.addEventListener('mousemove', t.moveHandler, true);
        t.body.addEventListener('mouseout', t.outHandler, true);
        t.lastX = me.clientX;
        t.lastY = me.clientY;
        t.moving = true;
      }
    }
  }

  function CeventUp  (that){
    this.handler = handler;
    var t = that;
    function handler (me){
      if (me.button==0 && t.moving)
        _doneMoving(t);
    }
  }

  function _doneMoving (t){
    t.body.removeEventListener('mousemove', t.moveHandler, true);
    t.body.removeEventListener('mouseout', t.outHandler, true);
    t.moving = false;
  }

  function CeventOut  (that){
    this.handler = handler;
    var t = that;
    function handler (me){
      if (me.button==0){
        t.moveHandler (me);
      }
    }
  }

  function CeventMove (that){
    this.handler = handler;
    var t = that;
    function handler (me){
      if (t.enabled && !t.wentOut){
        var newTop = parseInt(t.theDiv.style.top) + me.clientY - t.lastY;
        var newLeft = parseInt(t.theDiv.style.left) + me.clientX - t.lastX;
        if (newTop < t.bounds.top){     // if out-of-bounds...
          newTop = t.bounds.top;
          _doneMoving(t);
        } else if (newLeft < t.bounds.left){
          newLeft = t.bounds.left;
          _doneMoving(t);
        } else if (newLeft > t.bounds.right){
          newLeft = t.bounds.right;
          _doneMoving(t);
        } else if (newTop > t.bounds.bot){
          newTop = t.bounds.bot;
          _doneMoving(t);
        }
        t.theDiv.style.top = newTop + 'px';
        t.theDiv.style.left = newLeft + 'px';
        t.lastX = me.clientX;
        t.lastY = me.clientY;
      }
    }
  }

  function debug  (msg, e){
    logit ("*************** "+ msg +" ****************");
    logit ('clientWidth, Height: '+ e.clientWidth +','+ e.clientHeight);
    logit ('offsetLeft, Top, Width, Height (parent): '+ e.offsetLeft +','+ e.offsetTop +','+ e.offsetWidth +','+ e.offsetHeight +' ('+ e.offsetParent +')');
    logit ('scrollLeft, Top, Width, Height: '+ e.scrollLeft +','+ e.scrollTop +','+ e.scrollWidth +','+ e.scrollHeight);
  }

  function dispEvent (msg, me){
    logit (msg + ' Button:'+ me.button +' Screen:'+ me.screenX +','+ me.screenY +' client:'+  me.clientX +','+ me.clientY +' rTarget: '+ me.relatedTarget);
  }
}

function inspect(obj, maxLevels, level, doFunctions){
  var str = '', type, msg;
  if(level == null)  level = 0;
  if(maxLevels == null) maxLevels = 1;
  if(maxLevels < 1)
      return 'Inspect Error: Levels number must be > 0';
  if(obj == null)
    return 'ERROR: Object is NULL\n';
  var indent = '';
  for (var i=0; i<level; i++)
    indent += '  ';
  for(property in obj) {
    try {
        type =  matTypeof(obj[property]);
        if (doFunctions==true && (type == 'function')){
          str += indent + '(' + type + ') ' + property + "[FUNCTION]\n";
        } else if (type != 'function') {
          str += indent + '(' + type + ') ' + property + ( (obj[property]==null)?(': null'):('')) +' = '+ obj[property] +"\n";
        }
        if((type=='object' || type=='array') && (obj[property] != null) && (level+1 < maxLevels))
          str += inspect(obj[property], maxLevels, level+1, doFunctions);  // recurse
    }
    catch(err) {
      // Is there some properties in obj we can't access? Print it red.
      if(typeof(err) == 'string') msg = err;
      else if(err.message)        msg = err.message;
      else if(err.description)    msg = err.description;
      else                        msg = 'Unknown';
      str += '(Error) ' + property + ': ' + msg +"\n";
    }
  }
  str += "\n";
  return str;
}

Array.prototype.compare = function(testArr) {
    if (this.length != testArr.length) return false;
    for (var i = 0; i < testArr.length; i++) {
        if (this[i].compare) { 
            if (!this[i].compare(testArr[i])) return false;
        }
        if (this[i] !== testArr[i]) return false;
    }
    return true;
}

function objectName (o){
  var s = o.toString();
  return s.substr(7,s.length-8);
}

function matTypeof (v){
  if (typeof (v) == 'object'){
    if (!v)
      return 'null';
//    else if (unsafeWindow.Object.prototype.toString.apply(v) === '[object Array]')
    else if (v.constructor.toString().indexOf("Array")>=0 && typeof(v.splice)=='function')
      return 'array';
    else return 'object';
  }
  return typeof (v);
}


function tbodyScroller (tbody, maxHeight){  
  tbody.style.maxHeight = '';
  tbody.style.height = '';
  tbody.style.overflowX = 'hidden';
  if (parseInt(tbody.clientHeight) > maxHeight){
    tbody.style.height = maxHeight + 'px';
    tbody.style.maxHeight = maxHeight + 'px';
    tbody.style.overflowY = 'auto';
  }
}
function getRemainingHeight (e, cont){
  var ec = getClientCoords(e);
  var cc = getClientCoords(cont);
  return cont.clientHeight - (ec.y - cc.y);
}


function addCommasInt(n){
  nStr = parseInt(n) + '';
  var rgx = /(\d+)(\d{3})/;
  while (rgx.test(nStr)) {
    nStr = nStr.replace(rgx, '$1' + ',' + '$2');
  }
  return nStr;
}

function addCommas(nStr){
  nStr += '';
  x = nStr.split('.');
  x1 = x[0];
  x2 = x.length > 1 ? '.' + x[1] : '';
  var rgx = /(\d+)(\d{3})/;
  while (rgx.test(x1)) {
    x1 = x1.replace(rgx, '$1' + ',' + '$2');
  }
  return x1 + x2;
}

function unixTime (){
  return parseInt (new Date().getTime() / 1000) + unsafeWindow.g_timeoff;
}
function htmlOptions (a, curVal){
  m = '';
  for (k in a)
    m += '<OPTION value="'+ k +'"'+ (k==curVal?' SELECTED':'')  +'>'+ a[k] +'</option>';
  return m;
}
function getFunctionName (func){
  var name=/\W*function\s+([\w\$]+)\(/.exec(func);
  if (!name)
    return '';
  return name[1];
}

function findAllBetween (txt, find1, find2){
  var m = [];
  var last = 0;
  while ( (i1=txt.indexOf(find1, last))>=0 && (i2=txt.indexOf (find2, i1))>=0 ) {
    m.push (txt.substring(i1+find1.length, i2));
    last = i2 + find2.length;
  }
  return m;
}

function strUpTo (s, find){
  var i = s.indexOf(find);
  if (i > 0)
    return s.substr(0, i);
  return s;
}

/********
 Xd Xh
 Xh Xm
 Xm Xs
 Xs
********/
function timestrShort(time) {
  time = parseInt (time);
  if (time > 86400){
    var m = [];
    time /= 3600;
    m.push (parseInt(time/24)); 
    m.push ('d ');
    m.push (parseInt(time%24)); 
    m.push ('h ');
    return m.join ('');    
  } else
    return timestr (time);
}

/**********************
 part       full
 Xd Xh Xm   Xd Xh Xm Xs
 Xh Xm      Xh Xm Xs
 Xm Xs      Xm Xs
 Xs         Xs
**********************/
function timestr(time, full) {
  time = parseInt (time);
  var m = [];
  var t = time;
  if (t < 61)
    return  t + 's';
  if (t > 86400){
    m.push (parseInt(t/86400)); 
    m.push ('d ');
    t %= 86400;
  }  
  if (t>3600 || time>3600){
    m.push (parseInt(t/3600)); 
    m.push ('h ');
    t %= 3600;
  }  
  m.push (parseInt(t/60)); 
  m.push ('m');
  if (full || time<=3600 ){
    m.push (' ');
    m.push (t%60);
    m.push ('s');  
  }
  return m.join ('');
}

/************  LIB singletons .... **************/
// TODO: fix REopening window
var WINLOG_MAX_ENTRIES = 1000;     // TODO
var WinLog = {
  state : null,
  win: null,
  eOut : null,
  lastE : null,
  enabled : true,
  reverse : true,
  busy : false,
isOpening : false,

  open : function (){
    var t = WinLog;

    function eventButClear(){
      var t = WinLog;
      t.lastE = null;
      t.eOut.innerHTML ='';
    }
    function eventButReverse(){
      var t = WinLog;
      if (t.busy)
        return;
      t.busy = true;
      if (t.reverse){
        t.win.document.getElementById('wlRev').value= 'Top';
        t.reverse = false;
      } else{
        t.win.document.getElementById('wlRev').value= 'Bottom';
        t.reverse = true;
      }
      var n = t.eOut.childNodes.length;
      if (n < 2)
        return;
      for (i=n-2; i>=0; i--){
        t.eOut.appendChild (t.eOut.childNodes[i]);
      }
      t.busy = false;
    }
    
    if (!t.win || t.win.closed){
t.isOpening = true;  
// Firefox bug??? It appears as if a new thread is started on open, withOUT reusing same window? huh?
      t.win = window.open('', 'uwtrace', 'top=30,left=0,width=750,height=700,scrollbars=no,location=no,menubar=no,directories=no,status=no');
t.isOpening = false; 
t.state = null; 
    }
    
    if (t.state == null){
      t.win.document.body.innerHTML = '<STYLE>pre{margin:0px} hr{margin:3px; height:1px; border:0px; color:#cee; background-color:#cee}</style>\
        <BODY style="margin:0px; padding:0px; border:none">\
        <DIV id=winlogtop style="background-color:#d0d0d0; margin:0px; padding:0px; border:1px solid">\
        <INPUT id=wlClear type=submit value="Clear"> &nbsp; <INPUT id=wlRev type=submit value="Bottom"></div>\
        <DIV id=wlOut style="overflow-y:auto; height:100%; max-height:100%"></div></body>';
      t.win.document.getElementById('wlClear').addEventListener('click', eventButClear, false);
      t.win.document.getElementById('wlRev').addEventListener('click', eventButReverse, false);
      t.eOut =  t.win.document.getElementById('wlOut');
      t.lastE = null;
      t.state = 1;
    }
  },

  writeText : function (msg){
    var t = WinLog;
    if (!t.enabled || t.isOpening)
      return;
    t.write (t.gtlt(msg));
  },
  
  write : function (msg){
    var t = WinLog;
    if (!t.enabled || t.isOpening)
      return;
    t.open();
    var te = document.createElement('pre');
    var now = new Date();
    var m = [];
    var millis = now.getMilliseconds();
    m.push (now.toTimeString().substring (0,8));
    m.push ('.');
    if (millis<100)
      m.push('0');
    if (millis<10)
      m.push('0');
    m.push(millis);
    m.push (': ');
    m.push (msg);
    te.innerHTML = m.join('');
    if (t.reverse){
      if (t.lastE == null){
        t.eOut.appendChild(te);
        t.lastE = te;
      } else {
        t.eOut.insertBefore(te, t.lastE);
      }
      var hr = document.createElement('hr');
      t.eOut.insertBefore(hr, te);
      t.lastE = hr;
    } else {
      t.eOut.appendChild(te);
      t.eOut.appendChild(document.createElement('hr'));
    }
  },
  
  gtlt : function (s){
    s = s.toString().replace ('<', '&lt;', 'g');
    return s.replace ('>', '&gt;', 'g');
  },

};

    
/*********************************** Gifts TAB ***********************************/
function explodeUrlArgs (url){
  var i = url.indexOf ('?');
  var a = url.substr(i+1).split ('&'); 
  var args = {};
  for (i=0; i<a.length; i++){
    var s = a[i].split ('=');
    args[s[0]] = s[1];
  }
  return args;
}


// returns: page text or null on comm error
function GM_AjaxPost (url, args, notify, label){
  if (ENABLE_GM_AJAX_TRACE) WinLog.writeText ('GM_AjaxPost ('+ label +'): ' + url +'\n'+ inspect (args, 5, 1));
  GM_xmlhttpRequest({
    method: "post",
    url: url,
    data: implodeUrlArgs(args),
    headers: { "Content-Type": "application/x-www-form-urlencoded", 'X-Requested-With': 'XMLHttpRequest', 'X-Prototype-Version': '1.6.1',
               'Accept': 'text/javascript, text/html, application/xml, text/xml, */*' },
    onload: function (rslt) {
      if (ENABLE_GM_AJAX_TRACE) WinLog.writeText ( 'GM_AjaxPost.onLoad ('+ label +'):\n '  + inspect (rslt, 6, 1));   
      notify (rslt.responseText);
    },
    onerror: function () {
      notify (null);
    },
  });  
}
// returns: page text or null on comm error
function GM_AjaxGet (url, args, notify, label){
  if (ENABLE_GM_AJAX_TRACE) WinLog.writeText ('GM_AjaxGet ('+ label +'): ' + url);
  GM_xmlhttpRequest({
    method: "get",
    url: addUrlArgs(url, args),
    onload: function (rslt) {
      if (ENABLE_GM_AJAX_TRACE) WinLog.writeText ( 'GM_AjaxGet.onLoad ('+ label +')  len='+ rslt.responseText.length +':\n '  + inspect (rslt, 6, 1));   
      notify (rslt.responseText);
    },
    onerror: function () {
      notify (null);
    },
  });  
}